For R beginners

New chunk Ctrl+Alt+I

Execute chunk Ctrl+Shift+Enter

Execute all chunks Ctrl+Alt+R

HTML preview Ctrl+Shift+K

Library preparations

library(readr)
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(tidyverse)
── Attaching core tidyverse packages ────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(ggplot2)
library(reshape2)

Attaching package: ‘reshape2’

The following object is masked from ‘package:tidyr’:

    smiths
library(stats)
library(tm)
Loading required package: NLP

Attaching package: ‘NLP’

The following object is masked from ‘package:ggplot2’:

    annotate
library(text2vec)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
library(textstem)
Loading required package: koRpus.lang.en
Loading required package: koRpus
Loading required package: sylly
For information on available language packages for 'koRpus', run

  available.koRpus.lang()

and see ?install.koRpus.lang()


Attaching package: ‘koRpus’

The following object is masked from ‘package:tm’:

    readTagged

The following object is masked from ‘package:readr’:

    tokenize

Data Import

data_posts <- read.csv("~/4year/2semester/dtII/CSVs/HEIs.csv",
                 colClasses = c(tweet_id = "character"))

# Modifying created_at type so that attribute can be used more easily 
data_posts$created_at <- as.POSIXct(data_posts$created_at,
                              format= "%Y-%m-%dT%H:%M:%S", tz="UTC")

#View(data)
summary(data_posts)
      id              tweet_id             text               type           bookmark_count    favorite_count     retweet_count      reply_count      
 Length:11728       Length:11728       Length:11728       Length:11728       Min.   :  0.000   Min.   :    0.00   Min.   :   0.00   Min.   :   0.000  
 Class :character   Class :character   Class :character   Class :character   1st Qu.:  0.000   1st Qu.:    7.00   1st Qu.:   2.00   1st Qu.:   0.000  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :  0.000   Median :   20.00   Median :   5.00   Median :   1.000  
                                                                             Mean   :  1.543   Mean   :   60.67   Mean   :  10.62   Mean   :   3.888  
                                                                             3rd Qu.:  1.000   3rd Qu.:   57.00   3rd Qu.:  11.00   3rd Qu.:   3.000  
                                                                             Max.   :418.000   Max.   :41655.00   Max.   :4214.00   Max.   :2317.000  
                                                                                                                                                      
   view_count        created_at                       hashtags             urls            media_type         media_urls       
 Min.   :      5   Min.   :2022-08-01 03:05:11.00   Length:11728       Length:11728       Length:11728       Length:11728      
 1st Qu.:   2643   1st Qu.:2022-10-19 12:56:27.00   Class :character   Class :character   Class :character   Class :character  
 Median :   6240   Median :2023-01-29 08:26:30.00   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
 Mean   :  14182   Mean   :2023-01-30 07:39:34.96                                                                              
 3rd Qu.:  16058   3rd Qu.:2023-05-05 14:16:43.25                                                                              
 Max.   :7604544   Max.   :2023-08-31 20:50:01.00                                                                              
 NA's   :4840                                                                                                                  

Initial Data Preparation

# Count of how many entries each HEI has
number_posts <- data_posts %>%
              group_by(id) %>% summarise(count = n())

number_posts

Since complutense only has 1 entry we can’t learn anything from it, so we removed it

data_posts <- data_posts[data_posts$id != "complutense.csv", ]

Visualization of all posts, just tweets and just replies

number_posts <- data_posts %>%
              group_by(id) %>% summarise(posts = n())

number_tweets <- data_posts[data_posts$type == "Tweet", ] %>%
              group_by(id) %>% summarise(tweets = n())

number_replies <- data_posts[data_posts$type == "Reply", ] %>%
              group_by(id) %>% summarise(replies = n())

print(number_posts)
print(number_tweets)
print(number_replies)

Calculating the percentage of tweets and replies based on all posts

# Merging the counts of tweets and replies with the count of posts
data_ratio <- merge(number_posts, number_tweets, by = "id", all = TRUE)
data_ratio <- merge(data_ratio, number_replies, by = "id", all = TRUE)


data_ratio$percentage_tweets <- round(((data_ratio$tweets / data_ratio$posts) * 100), 2)
data_ratio$percentage_replies <- round(((data_ratio$replies / data_ratio$posts) * 100), 2)

data_ratio <- data_ratio[, c("id", "percentage_tweets", "percentage_replies")]

data_ratio$percentage_replies[is.na(data_ratio$percentage_replies)] <- 0

print(data_ratio)

NA removal

Function to visualize the number of NAs in all columns

na_count <- function(){
  # Counting the number of NA values for each column
  na_count <- colSums(is.na(data_posts))
  
  # Creating a new data frame with the NA counts
  na_counts_table <- data.frame(Column = names(na_count), NA_Count = na_count)
  
  print(na_counts_table)
}

Calculations of view, favourite, retweet and reply percentiles and visualization of NAs in all columns

data_posts <- data_posts %>%
  group_by(id) %>%
  mutate(view_percentile = ntile(view_count, 100),
         favorite_percentile = ntile(favorite_count, 100),
         retweet_percentile = ntile(retweet_count, 100),
         reply_percentile = ntile(reply_count, 100)) %>%
  rowwise() %>%
  mutate(avg_percentile = round(mean(c(view_percentile, favorite_percentile, retweet_percentile, reply_percentile), na.rm = TRUE), 2))

na_count()

data_percentile <- data_posts[, c("id", "view_percentile", "favorite_percentile", "retweet_percentile", "reply_percentile", "avg_percentile")]

print(data_percentile)

Calculation of the maximum number of views for each HEI

max_view_counts <- tapply(data_posts$view_count, data_posts$id, max, na.rm = TRUE)

print(max_view_counts)
      duke.csv       epfl.csv        goe.csv    harvard.csv  leicester.csv manchester.csv        mit.csv         sb.csv   stanford.csv    trinity.csv 
        307969         105095          15455        2982704          47838         317086        7604544         607498         222593         205333 
        wv.csv       yale.csv 
        109265         143108 

Removal of NAs

# From view count
data_posts$view_count <- ifelse(
  is.na(data_posts$view_count),
  round(max_view_counts[data_posts$id] * (data_posts$avg_percentile / 100)),
  data_posts$view_count)

# From view percentile
data_posts$view_percentile <- ifelse(
  is.na(data_posts$view_percentile),
  data_posts$avg_percentile,
  data_posts$view_percentile)

Visualization of NAs in all columns

na_count()

Function to calculate average posts

average_posts <- function(timeframe){
  # Calculation of the timeframe between earliest and latest post for each HEI
  date_range <- data_posts %>%
    group_by(id) %>%
    summarise(min_date = min(created_at),
              max_date = max(created_at)) %>%
    mutate(num_days = as.numeric(difftime(max_date, min_date, units = timeframe)))
  
  # Naming the column respecting the timeframe
  column_name <- paste0("avg_posts_per_", timeframe)
  
  # Calculation of the number of posts per day for each HEI
  posts_per_timeframe <- number_posts %>%
    left_join(date_range, by = "id") %>%
    mutate(!!column_name := round((posts / num_days), 2))
  
  print(posts_per_timeframe)
  return(posts_per_timeframe)
}
posts_per_day <- average_posts("days")
posts_per_week <- average_posts("weeks")

Plot for the average number of posts per day for each HEI

barplot(posts_per_day$avg_posts_per_days,
        names.arg = posts_per_day$id,
        main = "Average Posts per Day",
        xlab = "HEI",
        ylab = "Average Number of Posts",
        ylim = c(0, max(posts_per_day$avg_posts_per_days) + 1),
        las = 2,
        col = "#3498DB")

# Adding text labels over each bar and aligning it with the center of each bar 
text(x = barplot(posts_per_day$avg_posts_per_days, plot = FALSE),
     y = posts_per_day$avg_posts_per_days,
     labels = round(posts_per_day$avg_posts_per_days, 2),
     pos = 3)

Plot for the average number of posts per week for each HEI

barplot(posts_per_week$avg_posts_per_weeks,
        names.arg = posts_per_week$id,
        main = "Average Posts per Week",
        xlab = "HEI",
        ylab = "Average Number of Posts",
        ylim = c(0, max(posts_per_week$avg_posts_per_weeks) + 5),
        las = 2,
        col = "#E74C3C")

text(x = barplot(posts_per_week$avg_posts_per_weeks, plot = FALSE),
     y = posts_per_week$avg_posts_per_weeks,
     labels = round(posts_per_week$avg_posts_per_weeks, 2),
     pos = 3)

Defining the intervals of time for the academic year

intervals <- list(
  interval1 = as.POSIXct(c("2022-08-31", "2022-12-15")),
  interval2 = as.POSIXct(c("2023-01-04", "2023-04-01")),
  interval3 = as.POSIXct(c("2023-04-14", "2023-06-15"))
)

Function to check if a date falls within a given interval of time and apply appropriate Boolean

check_interval <- function(date) {
  for (i in 1:length(intervals)) {
    interval_start <- intervals[[i]][1]
    interval_end <- intervals[[i]][2]
    if (date >= interval_start & date <= interval_end) {
      return(TRUE)
    }
  }
  return(FALSE)
}
data_posts$academic_year <- sapply(data_posts$created_at, check_interval)
print(data.frame(id = data_posts$id, academic_year = data_posts$academic_year))

Function to count number of posts and average per day during academic time and vacation time

analyze_posts <- function(academic_year_filter) {
  # Filtering the data based on the academic_year_filter
  filtered_data <- data_posts %>%
    filter(academic_year == academic_year_filter)
  
  # Count of days for each HEI
  unique_days <- filtered_data %>%
    group_by(id) %>%
    summarise(unique_days = n_distinct(as.Date(created_at)))
  
  # Count of posts for each HEI
  number_posts_boolean <- filtered_data %>%
    group_by(id) %>%
    summarise(count = n())
  
  # Naming the column respecting the time period
  time <- ifelse(academic_year_filter, "academic_time", "vacation_time")
  column_name <- paste0("avg_posts_in_", time)
  
  # Combination of data and calculation of average posts per day
  combined_data <- left_join(unique_days, number_posts_boolean, by = "id")
  combined_data <- combined_data %>%
    mutate(!!column_name := round((count / unique_days), 2))
  
  print(combined_data)
  return(combined_data)
}
data_posts_academic <- analyze_posts(TRUE)
data_posts_vacations <- analyze_posts(FALSE)

Plot for the average number of posts during academic time for each HEI

barplot(data_posts_academic$avg_posts_in_academic_time,
        names.arg = data_posts_academic$id,
        main = "Average Posts during Academic Time",
        xlab = "HEI",
        ylab = "Average Number of Posts",
        ylim = c(0, max(data_posts_academic$avg_posts_in_academic_time) + 5),
        las = 2,
        col = "#34495E")

text(x = barplot(data_posts_academic$avg_posts_in_academic_time, plot = FALSE),
     y = data_posts_academic$avg_posts_in_academic_time,
     labels = round(data_posts_academic$avg_posts_in_academic_time, 2),
     pos = 3)

Plot for the average number of posts during vacation time for each HEI

barplot(data_posts_vacations$avg_posts_in_vacation_time,
        names.arg = data_posts_vacations$id,
        main = "Average Posts during Vacation Time",
        xlab = "HEI",
        ylab = "Average Number of Posts",
        ylim = c(0, max(data_posts_vacations$avg_posts_in_vacation_time) + 5),
        las = 2,
        col = "#D35400")

text(x = barplot(data_posts_vacations$avg_posts_in_vacation_time, plot = FALSE),
     y = data_posts_vacations$avg_posts_in_vacation_time,
     labels = round(data_posts_vacations$avg_posts_in_vacation_time, 2),
     pos = 3)

Data preparation for dates

# Creating new table that contains a new column for the day of the week
data_posts_days <- data_posts %>%
  mutate(day_of_week = weekdays(created_at))

# Selecting only the id, created_at, and day_of_week columns for the new table
data_posts_days <- data_posts_days %>%
  select(id, created_at, day_of_week)

# Create column hour from created_at
data_posts_days$created_hour <- as.numeric(format(data_posts_days$created_at, "%H"))

print(data_posts_days)
# Grouping by id and day_of_week, then counting the number of posts
number_posts_days <- data_posts_days %>%
  group_by(id, day_of_week) %>%
  summarise(count = n())
`summarise()` has grouped output by 'id'. You can override using the `.groups` argument.
# Grouping by id, day_of_week and day created at, then counting th enumber of tweets
number_posts_per_day <- data_posts_days %>%
    mutate(created_date = as.Date(created_at)) %>% 
    group_by(id, day_of_week, created_date) %>%
    summarize(count = n())
`summarise()` has grouped output by 'id', 'day_of_week'. You can override using the `.groups` argument.
# Finding for each HEI the average count of posts per day
average_number_posts_per_day <- number_posts_per_day %>%
  group_by(id, day_of_week) %>%
  summarise(average_count = round(mean(count), 2))
`summarise()` has grouped output by 'id'. You can override using the `.groups` argument.
print(number_posts_days)

Highest and lowest posts

# Finding the HEI with the lowest count of posts per day
lowest_count <- number_posts_days %>%
  group_by(day_of_week) %>%
  slice_min(order_by = count) %>%
  select(day_of_week, id, count)

# Finding the HEI with the highest count of posts per day
highest_count <- number_posts_days %>%
  group_by(day_of_week) %>%
  slice_max(order_by = count) %>%
  select(day_of_week, id, count)

# Combine the results
high_low_HEI <- bind_rows(lowest_count, highest_count) %>%
  arrange(day_of_week)

print(high_low_HEI)

Plot for the highest and lowest count of posts per day for each day of the week

ggplot(high_low_HEI, aes(x = day_of_week, y = count, fill = id)) +
  geom_bar(stat = "identity", position = "dodge") +
  geom_text(aes(label = count),
            position = position_dodge(width = 0.9),
            vjust = -0.5,
            size = 3) +
  labs(title = "Highest and Lowest Count of Posts per Day for Each Day of the Week",
       x = "Day of the Week", y = "Count") +
  scale_fill_manual(values = rainbow(length(unique(high_low_HEI$id)))) +
  theme_minimal() +
  theme(legend.title = element_blank())

Average of posts

# Finding the HEI with lowest and highest averaged count of posts per day
high_low_average_HEIs <- average_number_posts_per_day %>%
  group_by(day_of_week) %>%
  filter(average_count == max(average_count) | average_count == min(average_count)) %>%
  arrange(day_of_week, ifelse(average_count == min(average_count), average_count, -average_count))

print(high_low_average_HEIs)

Plot for the highest and lowest average count of posts per day for each day of the week

ggplot(high_low_average_HEIs, aes(x = day_of_week, y = average_count, fill = id)) +
  geom_bar(stat = "identity", position = "dodge") +
  geom_text(aes(label = round(average_count, 2)),
            position = position_dodge(width = 0.7),
            vjust = -0.5,
            size = 3) +
  labs(title = "Highest and Lowest Average Count of Posts per Day for Each Day of the Week",
       x = "Day of the Week", y = "Average Count") +
  scale_fill_manual(values = rainbow(length(unique(high_low_HEI$id)))) +
  theme_minimal() +
  theme(legend.title = element_blank())

Favourite hour and day

favourite_day_hei <- number_posts_days %>%
  group_by(id) %>%
  top_n(1, count) %>%
  arrange(id)

print(favourite_day_hei)
number_posts_hours <- data_posts_days %>%
  group_by(id, created_hour) %>%
  summarise(count = n()) %>%
  ungroup()
`summarise()` has grouped output by 'id'. You can override using the `.groups` argument.
favourite_hour_hei <- number_posts_hours %>%
  group_by(id) %>%
  top_n(1, count) %>%
  arrange(id)

print(favourite_hour_hei)

Heatmaps

Function to plot heatmap for various HEIs

heatmap_maker <- function(target_id){
  # Filtering data for the specific HEI
  target_data <- data_posts_days %>%
    filter(id == target_id)
  
  # Grouping by day of the week and hour, and counting the number of tweets
  tweet_counts <- target_data %>%
    group_by(day_of_week, created_hour) %>%
    summarise(num_posts = n())
  
  # Plotting heatmap
  ggplot(tweet_counts, aes(x = day_of_week, y = created_hour, fill = num_posts)) +
    geom_tile() +
    scale_fill_gradient(low = "white", high = "blue") +
    labs(title = paste("Post Heatmap for", target_id),
         x = "Day of the week",
         y = "Hour of the day")
}

Plot of heatmap for each HEI

heatmap_maker("duke.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("epfl.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("goe.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("harvard.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("leicester.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("manchester.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("mit.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("sb.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("stanford.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("trinity.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("wv.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

heatmap_maker("yale.csv")
`summarise()` has grouped output by 'day_of_week'. You can override using the `.groups` argument.

Hashtags

# Transforming empty strings into NA
data_posts$hashtags[data_posts$hashtags == ""] <- NA

# Table with number of unique hashtags and percentage of usage
hashtags <- data_posts %>%
                group_by(id) %>%
                summarise(count = n(),
                          na = sum(is.na(hashtags)),
                          unique_hashtags = length(unique(hashtags)),
                          hashtag_percentage = round(((count - na) / count * 100), 2))

print(hashtags)

Plot for the count of unique hashtags for each HEI

barplot(hashtags$unique_hashtags,
        names.arg = hashtags$id,
        main = "Unique Hashtags for Each HEI",
        xlab = "HEI",
        ylab = "Count of Unique Hashtags",
        ylim = c(0, max(hashtags$unique_hashtags) + 50),
        las = 2,
        col= "#16A085")

text(x = barplot(hashtags$unique_hashtags, plot = FALSE),
     y = hashtags$unique_hashtags,
     labels = round(hashtags$unique_hashtags, 2),
     pos = 3)

Plot for the usage of hashtag for each HEI

barplot(hashtags$hashtag_percentage,
        names.arg = hashtags$id,
        main = "Hashtags Percentage for Each HEI",
        xlab = "HEI",
        ylab = "Hashtags Percentage",
        ylim = c(0, max(hashtags$hashtag_percentage) + 30),
        las = 2,
        col= "#F1C40F")

text(x = barplot(hashtags$hashtag_percentage, plot = FALSE),
     y = hashtags$hashtag_percentage,
     labels = round(hashtags$hashtag_percentage, 2),
     pos = 3)

URL usage

# Transforming empty strings into NA
data_posts$urls[data_posts$urls == ""] <- NA

# Table with number of post, number of NA and url percentage of usage
url_usage <- data_posts %>%
                group_by(id) %>%
                summarise(count = n(),
                          na = sum(is.na(urls)),
                          url_percentage = round(((count - na) / count * 100), 2))

print(url_usage)

Plot for the usage of hashtag for each HEI

barplot(url_usage$url_percentage,
        names.arg = url_usage$id,
        main = "Urls Percentage for Each HEI",
        xlab = "HEI",
        ylab = "Urls Percentage",
        ylim = c(0, max(url_usage$url_percentage) + 10),
        las = 2,
        col= "#8E44AD")

text(x = barplot(url_usage$url_percentage, plot = FALSE),
     y = url_usage$url_percentage,
     labels = round(url_usage$url_percentage, 2),
     pos = 3)

Text

data_posts_content <- data_posts %>%
            select(id, text)

# Counting number of words
data_posts_content <- data_posts_content %>%
  mutate(num_words = lengths(strsplit(text, "\\s+")))

# Grouping by HEI and calculate average, minimum, and maximum values of number of words
data_posts_content_metrics <- data_posts_content %>%
  group_by(id) %>%
  summarise(average_num_words = mean(num_words),
            min_num_words = min(num_words),
            max_num_words = max(num_words))
print(data_posts_content_metrics)

Plot for the average, maximum and minimum values of words for each HEI

ggplot(data_posts_content_metrics, aes(x = id, y = average_num_words)) +
  geom_point(aes(color = "Average")) +
  geom_errorbar(aes(ymin = min_num_words, ymax = max_num_words, color = "Range"), width = 0.2) +
  scale_color_manual(values = c("Average" = "#1976D2", "Range" = "#EF5350")) +
  labs(title = "Word Count Summary by HEI",
       x = "HEI",
       y = "Number of Words",
       color = "Metric") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))

Clusters

# Creating table for cluster algorithms

# Joining attribute percentage_tweets (percentage of tweets out of all posts) and percentage_replies (percentage of replies out of all posts) from number_posts also adding unique_hashtags (number of unique hashtags) and hashtag_percentage (percentage of posts that contain a hashtag) from hashtags, per HEI
cluster_table <- merge(select(hashtags, id, unique_hashtags, hashtag_percentage), select(data_ratio, id, percentage_tweets, percentage_replies), by = "id", all=TRUE)

# Joining attribute avg_posts_per_days (average of posts per day) from posts_per_day per HEI
cluster_table <- merge(cluster_table, select(posts_per_day, id, avg_posts_per_days), by = "id", all=TRUE)

# Joining attribute avg_posts_per_weeks (average of posts per week) from posts_per_week per HEI
cluster_table <- merge(cluster_table, select(posts_per_week, id, avg_posts_per_weeks), by = "id", all=TRUE)

# Joining attribute avg_posts_in_academic_time (average of posts during academic time)  from data_posts_academic per HEI
cluster_table <- merge(cluster_table, select(data_posts_academic, id, avg_posts_in_academic_time), by = "id", all=TRUE)

# Joining attribute avg_posts_in_vacation_time (average of posts during vacation time) from data_posts_vacations per HEI
cluster_table <- merge(cluster_table, select(data_posts_vacations, id, avg_posts_in_vacation_time), by = "id", all=TRUE)

# Joining attribute created_hour (hour where every HEI made more posts) from favourite_hour_hei per HEI
cluster_table <- merge(cluster_table, select(favourite_hour_hei, id, created_hour), by = "id", all=TRUE)

# Joining attribute url_percentage (percentage of posts that contain an url) from url_usage per HEI
cluster_table <- merge(cluster_table, select(url_usage, id, url_percentage), by = "id", all=TRUE)

# Joining attribute average_num_words (average number of words in the posts) from data_posts_content_metrics per HEI
cluster_table <- merge(cluster_table, select(data_posts_content_metrics, id, average_num_words), by = "id", all=TRUE)

print(cluster_table)

Function for cluster method

cluster_maker <- function(num_clusters, table){
  # Excluding id column for clustering
  cluster_data <- select(table, -id)
    
  # Scaling the data for kmeans method
  scaled_data <- scale(cluster_data)
  
  kmeans_model <- kmeans(scaled_data, centers = num_clusters, nstart = 10)

  # Extract cluster assignments
  cluster_assignments <- kmeans_model$cluster
  
  # Create a data frame combining original data with cluster assignments
  clustered_data <- cbind(cluster_table$id, cluster_data, cluster = cluster_assignments)
  
  clustered_data <- clustered_data[, c("cluster_table$id", "cluster")]
  
  print(clustered_data)
}

Function to discover best number of clusters

elbow_maker <- function(table){
  cluster_data <- select(table, -id)
  scaled_data <- scale(cluster_data)
  
  wss <- vector()
  range <- 1:10
  
  for (k in range) {
    kmeans_model <- kmeans(scaled_data, centers = k, nstart = 10)
    wss[k] <- kmeans_model$tot.withinss
  }
  
  elbow_df <- data.frame(k = range, WSS = wss)
  ggplot(elbow_df, aes(x = k, y = WSS)) +
    geom_line() +
    geom_point() +
    labs(x = "Number of Clusters", y = "Within-Cluster Sum of Squares (WCSS)",
         title = "Elbow Method for Optimal k") +
    theme_minimal()
}

Plot of Elbow Method and selection of best number of cluster to view how HEIs are grouped

elbow_maker(cluster_table)

cluster_maker(3, cluster_table)

2 Parte

cleanup <- function(docs, spec.words=NULL){
  # lowercase
  docs <- tm_map(docs, content_transformer(tolower))
  # rm numbers
  docs <- tm_map(docs, removeNumbers)
  # rm english common stopWords
  docs <- tm_map(docs, removeWords, stopwords("english"))
  # if stopwords are specified as a character vector
  if(!is.null(spec.words))
    docs <- tm_map(docs, removeWords, spec.words)
  # rm punctuations
  docs <- tm_map(docs, removePunctuation)
  # rm extra white spaces
  docs <- tm_map(docs, stripWhitespace)
  # lemmatizing text
  docs <- tm_map(docs, lemmatize_words)
  
  docs
}
pairing <- function(word_dict, paired_words) {
  if (nrow(word_dict) != length(paired_words)) {
    stop("The number of rows in word_dict and the length of paired_words should be equal.")
  }
  
  word_dict <- tibble::tibble(word = word_dict$value)
  result <- tibble::tibble(word = word_dict$word, category = paired_words)
  return(result)
}
classify_text <- function(text, word_pair) {
  # Tokenizing the text and converting to lowercase
  words <- tolower(unlist(strsplit(text, "\\W+")))
  
  # Finding the frequent words in the text
  freq_words <- words[words %in% word_pair$word]
  
  if (length(freq_words) == 0) {
    return("Unknown")  # Returning Unknown if no frequent words are found
  }
  
  # Getting the corresponding categories
  categories <- word_pair$category[word_pair$word %in% freq_words]
  
  # Sorting categories by frequency in descending order and return the most frequent one
  return(names(sort(table(categories), decreasing = TRUE))[1])
}
corpus_maker <- function(text) {
  texts <- text$text
  vc <- VectorSource(texts)
  corpus <- Corpus(vc)
  
  return(corpus)
}
freq_terms <- function(clean_text, number) {
  dtm <- DocumentTermMatrix(clean_text)
  dtm.tfidf <- weightTfIdf(dtm)
  
  mdf <- as_tibble(as.matrix(dtm.tfidf))
  
  mdf.freq <- mdf %>%
  select(findFreqTerms(dtm, number)) %>%
  summarise_all(sum) %>%
  gather() %>%
  arrange(desc(value))

  mdf.freq$key <- 
    factor(mdf.freq$key,
           levels = mdf.freq$key[order(mdf.freq$value)])
  
  word_dictionary <- as_tibble(mdf.freq$key)
  
  ggplot(mdf.freq, aes(x=key, y=value)) +
    geom_bar(stat="identity") +
    labs(x="terms", y="freq") + coord_flip()
  
  return(word_dictionary)
}

Duke analysis

duke_text <- subset(data_posts_content, id == "duke.csv")
duke_corpus <- corpus_maker(duke_text)

duke_clean_text <-cleanup(duke_corpus, c("new", "will", "change", "north", "can", "first", "year", "carolina", "years", "summer", "meet", "one"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
duke_word_dictionary <- freq_terms(duke_clean_text, 30)

duke_word_category <- c("Image", "Education", "Education", "Image", "Image", "Research", "Research", "Image", "Image", "Engagement", "Research", "Research", "Image", "Education", "Education", "Society", "Education", "Engagement")

duke_word_pair <- pairing(duke_word_dictionary, duke_word_category)

print(duke_word_pair)

# Applying the classify_text function to each text
duke_text <- duke_text %>%
  mutate(category = sapply(text, classify_text, word_pair = duke_word_pair))

print(duke_text)

epfl

epfl_text <- subset(data_posts_content, id == "epfl.csv")
epfl_corpus <- corpus_maker(epfl_text)

epfl_clean_text <- cleanup(epfl_corpus, c("new", "amp", "can", "will", "one", "now", "–", "via", "portrait"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
epfl_word_dictionary <- freq_terms(epfl_clean_text, 30)

epfl_word_category <- c("Image", "Research", "Image", "Research", "Research", "Research", "Education", "Image", "Research", "Research", "Education", "Research", NA, "Education", "Education")

epfl_word_pair <- pairing(epfl_word_dictionary, epfl_word_category)

print(epfl_word_pair)

# Applying the classify_text function to each text
epfl_text <- epfl_text %>%
  mutate(category = sapply(text, classify_text, word_pair = epfl_word_pair))

print(epfl_text)

goe

goe_text <- subset(data_posts_content, id == "goe.csv")
goe_corpus <- corpus_maker(goe_text)

goe_clean_text <- cleanup(goe_corpus, c("can", "new", "amp", "will"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
goe_word_dictionary <- freq_terms(goe_clean_text, 15)

goe_word_category <- c("Research", "Research", "Image", "Education", "Engagement", "Image", "Engagement", "Education")

goe_word_pair <- pairing(goe_word_dictionary, goe_word_category)

print(goe_word_pair)

# Applying the classify_text function to each text
goe_text <- goe_text %>%
  mutate(category = sapply(text, classify_text, word_pair = goe_word_pair))

print(goe_text)

harvard

harvard_text <- subset(data_posts_content, id == "harvard.csv")
harvard_corpus <- corpus_maker(harvard_text)

harvard_clean_text <- cleanup(harvard_corpus, c("new", "can", "will", "summer", "year", "first", "may", "-", "recent", "years", "one", "said", "time", "many", "world", "change"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
harvard_word_dictionary <- freq_terms(harvard_clean_text, 50)

harvard_word_category <- c("Image", "Education", "Research", "Image", "Research", "Research", "Engagement", "Education", "Society", "Education", "Society", "Research", "Education", "Image", "Research", NA, "Research", "Education", "Education", NA, "Research", "Society", "Society", "Research", "Education")

harvard_word_pair <- pairing(harvard_word_dictionary, harvard_word_category)

print(harvard_word_pair)

harvard_text <- harvard_text %>%
  mutate(category = sapply(text, classify_text, word_pair = harvard_word_pair))

print(harvard_text)

leicester

leicester_text <- subset(data_posts_content, id == "leicester.csv")
leicester_corpus <- corpus_maker(leicester_text)

leicester_clean_text <- cleanup(leicester_corpus, c("👉", "day", "new", "clear", "year", "can", "will", "time", "space", "one", "first"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
leicester_word_dictionary <- freq_terms(leicester_clean_text, 60)

leicester_word_category <- c("Image", NA, "Image", "Engagement", "Society", "Education", "Education", "Research", "Engagement", "Engagement", "Image", "Engagement", "Education", "Engamement", "Engagement", "Education", "Image", "Engagement", "Education", NA, "Image")

leicester_word_pair <- pairing(leicester_word_dictionary, leicester_word_category)

print(leicester_word_pair)

leicester_text <- leicester_text %>%
  mutate(category = sapply(text, classify_text, word_pair = leicester_word_pair))

print(leicester_text)

manchester

manchester_text <- subset(data_posts_content, id == "manchester.csv")
manchester_corpus <- corpus_maker(manchester_text)

manchester_clean_text <- cleanup(manchester_corpus, c("👇", "can", "just", "will", "get", "well", "one", "help", "now", "new", "read", "congratulations"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
manchester_word_dictionary <- freq_terms(manchester_clean_text, 50)

manchester_word_category <- c(NA, "Image", "Engagement", "Image", "Education", "Education", "Engagement", "Image", "Education", "Society", "Research", "Research", "Education", "Education", "Education", "Engagement", "Research", "Education", "Research")

manchester_word_pair <- pairing(manchester_word_dictionary, manchester_word_category)

print(manchester_word_pair)

manchester_text <- manchester_text %>%
  mutate(category = sapply(text, classify_text, word_pair = manchester_word_pair))

print(manchester_text)

mit

mit_text <- subset(data_posts_content, id == "mit.csv")
mit_corpus <- corpus_maker(mit_text)

mit_clean_text <- cleanup(mit_corpus, c("new", "can", "says", "“", "’", "—", "", "may", "first", "will", "using", "way", "one", "science"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
mit_word_dictionary <- freq_terms(mit_clean_text, 40)

mit_word_category <- c("Image", "Research", NA, "Image", NA, "Education", "Research", NA, "Education", "Research", "Research", "Education", "Research", "Research", "Image", "Education", "Education", "Research", "Research", "Research", "Research", "Research", "Research", "Research", "Research", "Society")

mit_word_pair <- pairing(mit_word_dictionary, mit_word_category)

print(mit_word_pair)

mit_text <- mit_text %>%
  mutate(category = sapply(text, classify_text, word_pair = mit_word_pair))

print(mit_text)

sb

sb_text <- subset(data_posts_content, id == "sb.csv")
sb_corpus <- corpus_maker(sb_text)

sb_clean_text <- cleanup(sb_corpus, c("new", "—", "will", "week", "can", "amp", "via", "now", "future", "first"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
sb_word_dictionary <- freq_terms(sb_clean_text, 30)

sb_word_category <- c("Image", "Research", "Image", "Image", "Image", NA, "Research", "Image", "Image", "Research", "Education", "Image", "Education", "Image", "Education", "Image", "Society")

sb_word_pair <- pairing(sb_word_dictionary, sb_word_category)

print(sb_word_pair)

sb_text <- sb_text %>%
  mutate(category = sapply(text, classify_text, word_pair = sb_word_pair))

print(sb_text)

stanford

stanford_text <- subset(data_posts_content, id == "stanford.csv")
stanford_corpus <- corpus_maker(stanford_text)

stanford_clean_text <- cleanup(stanford_corpus, c("new", "will"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
stanford_word_dictionary <- freq_terms(stanford_clean_text, 25)

stanford_word_category <- c("Image", "Image", "Research", "Education", NA)

stanford_word_pair <- pairing(stanford_word_dictionary, stanford_word_category)

print(stanford_word_pair)

stanford_text <- stanford_text %>%
  mutate(category = sapply(text, classify_text, word_pair = stanford_word_pair))

print(stanford_text)

trinity

trinity_text <- subset(data_posts_content, id == "trinity.csv")
trinity_corpus <- corpus_maker(trinity_text)

trinity_clean_text <- cleanup(trinity_corpus, c("amp", "read", "new", "can", "will", "week", "work", "great", "day", "visit", "irish", "first", "led", "congratulations"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
trinity_word_dictionary <- freq_terms(trinity_clean_text, 40)
Warning: empty document(s): 754
trinity_word_category <- c("Image", "Education", "Research", "Image", "Research", "Image", "Research", "Society", "Society", "Education", "Engagement", "Engagement", "Education", "Image", "Education", "Image", "Research")

trinity_word_pair <- pairing(trinity_word_dictionary, trinity_word_category)

print(trinity_word_pair)

trinity_text <- trinity_text %>%
  mutate(category = sapply(text, classify_text, word_pair = trinity_word_pair))

print(trinity_text)

wv

wv_text <- subset(data_posts_content, id == "wv.csv")
wv_corpus <- corpus_maker(wv_text)

wv_clean_text <- cleanup(wv_corpus, c("💛💙", "🙌", "👉", "happy", "day", "see", "week", "great", "can", "will", "well", "know", "new", "now", "get", "just"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
wv_word_dictionary <- freq_terms(wv_clean_text, 40)

wv_word_category <- c(NA, "Image", "Image", NA, "Image", "Image", "Engagement", "Education", "Education", "Education", NA)

wv_word_pair <- pairing(wv_word_dictionary, wv_word_category)

print(wv_word_pair)

wv_text <- wv_text %>%
  mutate(category = sapply(text, classify_text, word_pair = wv_word_pair))

print(wv_text)

yale

yale_text <- subset(data_posts_content, id == "yale.csv")
yale_corpus <- corpus_maker(yale_text)

yale_clean_text <- cleanup(yale_corpus, c("new", "—", "will", "can", "'", "first", "work", "read", "help", "year"))
Warning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documentsWarning: transformation drops documents
yale_word_dictionary <- freq_terms(yale_clean_text, 70)

yale_word_category <- c("Research", "Education", "Education", NA, "Research", "Research", "Research", "Research", "Education", "Image", "Research", "Education", "Image", "Research", NA, "Research", "Image", "Image", "Research", "Image", "Research", "Society", "Research", "Society", "Society")

yale_word_pair <- pairing(yale_word_dictionary, yale_word_category)

print(yale_word_pair)

yale_text <- yale_text %>%
  mutate(category = sapply(text, classify_text, word_pair = yale_word_pair))

print(yale_text)
LS0tCnRpdGxlOiAiUHJvamVjdCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQojIyMgRm9yIFIgYmVnaW5uZXJzCk5ldyBjaHVuayAqQ3RybCtBbHQrSSoKCkV4ZWN1dGUgY2h1bmsgKkN0cmwrU2hpZnQrRW50ZXIqCgpFeGVjdXRlIGFsbCBjaHVua3MgKkN0cmwrQWx0K1IqCgpIVE1MIHByZXZpZXcgKkN0cmwrU2hpZnQrSyoKCiMgTGlicmFyeSBwcmVwYXJhdGlvbnMKCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KHN0YXRzKQpsaWJyYXJ5KHRtKQpsaWJyYXJ5KHRleHQydmVjKQpsaWJyYXJ5KHRleHRzdGVtKQpgYGAKCiMgRGF0YSBJbXBvcnQKCmBgYHtyfQpkYXRhX3Bvc3RzIDwtIHJlYWQuY3N2KCJ+LzR5ZWFyLzJzZW1lc3Rlci9kdElJL0NTVnMvSEVJcy5jc3YiLAogICAgICAgICAgICAgICAgIGNvbENsYXNzZXMgPSBjKHR3ZWV0X2lkID0gImNoYXJhY3RlciIpKQoKIyBNb2RpZnlpbmcgY3JlYXRlZF9hdCB0eXBlIHNvIHRoYXQgYXR0cmlidXRlIGNhbiBiZSB1c2VkIG1vcmUgZWFzaWx5IApkYXRhX3Bvc3RzJGNyZWF0ZWRfYXQgPC0gYXMuUE9TSVhjdChkYXRhX3Bvc3RzJGNyZWF0ZWRfYXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvcm1hdD0gIiVZLSVtLSVkVCVIOiVNOiVTIiwgdHo9IlVUQyIpCgojVmlldyhkYXRhKQpzdW1tYXJ5KGRhdGFfcG9zdHMpCmBgYAoKIyBJbml0aWFsIERhdGEgUHJlcGFyYXRpb24KCmBgYHtyfQojIENvdW50IG9mIGhvdyBtYW55IGVudHJpZXMgZWFjaCBIRUkgaGFzCm51bWJlcl9wb3N0cyA8LSBkYXRhX3Bvc3RzICU+JQogICAgICAgICAgICAgIGdyb3VwX2J5KGlkKSAlPiUgc3VtbWFyaXNlKGNvdW50ID0gbigpKQoKbnVtYmVyX3Bvc3RzCmBgYAoKIyBTaW5jZSBjb21wbHV0ZW5zZSBvbmx5IGhhcyAxIGVudHJ5IHdlIGNhbid0IGxlYXJuIGFueXRoaW5nIGZyb20gaXQsIHNvIHdlIHJlbW92ZWQgaXQKCmBgYHtyfQpkYXRhX3Bvc3RzIDwtIGRhdGFfcG9zdHNbZGF0YV9wb3N0cyRpZCAhPSAiY29tcGx1dGVuc2UuY3N2IiwgXQpgYGAKCiMgVmlzdWFsaXphdGlvbiBvZiBhbGwgcG9zdHMsIGp1c3QgdHdlZXRzIGFuZCBqdXN0IHJlcGxpZXMKCmBgYHtyfQpudW1iZXJfcG9zdHMgPC0gZGF0YV9wb3N0cyAlPiUKICAgICAgICAgICAgICBncm91cF9ieShpZCkgJT4lIHN1bW1hcmlzZShwb3N0cyA9IG4oKSkKCm51bWJlcl90d2VldHMgPC0gZGF0YV9wb3N0c1tkYXRhX3Bvc3RzJHR5cGUgPT0gIlR3ZWV0IiwgXSAlPiUKICAgICAgICAgICAgICBncm91cF9ieShpZCkgJT4lIHN1bW1hcmlzZSh0d2VldHMgPSBuKCkpCgpudW1iZXJfcmVwbGllcyA8LSBkYXRhX3Bvc3RzW2RhdGFfcG9zdHMkdHlwZSA9PSAiUmVwbHkiLCBdICU+JQogICAgICAgICAgICAgIGdyb3VwX2J5KGlkKSAlPiUgc3VtbWFyaXNlKHJlcGxpZXMgPSBuKCkpCgpwcmludChudW1iZXJfcG9zdHMpCnByaW50KG51bWJlcl90d2VldHMpCnByaW50KG51bWJlcl9yZXBsaWVzKQpgYGAKCiMgQ2FsY3VsYXRpbmcgdGhlIHBlcmNlbnRhZ2Ugb2YgdHdlZXRzIGFuZCByZXBsaWVzIGJhc2VkIG9uIGFsbCBwb3N0cwoKYGBge3J9CiMgTWVyZ2luZyB0aGUgY291bnRzIG9mIHR3ZWV0cyBhbmQgcmVwbGllcyB3aXRoIHRoZSBjb3VudCBvZiBwb3N0cwpkYXRhX3JhdGlvIDwtIG1lcmdlKG51bWJlcl9wb3N0cywgbnVtYmVyX3R3ZWV0cywgYnkgPSAiaWQiLCBhbGwgPSBUUlVFKQpkYXRhX3JhdGlvIDwtIG1lcmdlKGRhdGFfcmF0aW8sIG51bWJlcl9yZXBsaWVzLCBieSA9ICJpZCIsIGFsbCA9IFRSVUUpCgoKZGF0YV9yYXRpbyRwZXJjZW50YWdlX3R3ZWV0cyA8LSByb3VuZCgoKGRhdGFfcmF0aW8kdHdlZXRzIC8gZGF0YV9yYXRpbyRwb3N0cykgKiAxMDApLCAyKQpkYXRhX3JhdGlvJHBlcmNlbnRhZ2VfcmVwbGllcyA8LSByb3VuZCgoKGRhdGFfcmF0aW8kcmVwbGllcyAvIGRhdGFfcmF0aW8kcG9zdHMpICogMTAwKSwgMikKCmRhdGFfcmF0aW8gPC0gZGF0YV9yYXRpb1ssIGMoImlkIiwgInBlcmNlbnRhZ2VfdHdlZXRzIiwgInBlcmNlbnRhZ2VfcmVwbGllcyIpXQoKZGF0YV9yYXRpbyRwZXJjZW50YWdlX3JlcGxpZXNbaXMubmEoZGF0YV9yYXRpbyRwZXJjZW50YWdlX3JlcGxpZXMpXSA8LSAwCgpwcmludChkYXRhX3JhdGlvKQpgYGAKCiMgTkEgcmVtb3ZhbAoKIyBGdW5jdGlvbiB0byB2aXN1YWxpemUgdGhlIG51bWJlciBvZiBOQXMgaW4gYWxsIGNvbHVtbnMKCmBgYHtyfQpuYV9jb3VudCA8LSBmdW5jdGlvbigpewogICMgQ291bnRpbmcgdGhlIG51bWJlciBvZiBOQSB2YWx1ZXMgZm9yIGVhY2ggY29sdW1uCiAgbmFfY291bnQgPC0gY29sU3Vtcyhpcy5uYShkYXRhX3Bvc3RzKSkKICAKICAjIENyZWF0aW5nIGEgbmV3IGRhdGEgZnJhbWUgd2l0aCB0aGUgTkEgY291bnRzCiAgbmFfY291bnRzX3RhYmxlIDwtIGRhdGEuZnJhbWUoQ29sdW1uID0gbmFtZXMobmFfY291bnQpLCBOQV9Db3VudCA9IG5hX2NvdW50KQogIAogIHByaW50KG5hX2NvdW50c190YWJsZSkKfQpgYGAKCiMgQ2FsY3VsYXRpb25zIG9mIHZpZXcsIGZhdm91cml0ZSwgcmV0d2VldCBhbmQgcmVwbHkgcGVyY2VudGlsZXMgYW5kIHZpc3VhbGl6YXRpb24gb2YgTkFzIGluIGFsbCBjb2x1bW5zCgpgYGB7cn0KZGF0YV9wb3N0cyA8LSBkYXRhX3Bvc3RzICU+JQogIGdyb3VwX2J5KGlkKSAlPiUKICBtdXRhdGUodmlld19wZXJjZW50aWxlID0gbnRpbGUodmlld19jb3VudCwgMTAwKSwKICAgICAgICAgZmF2b3JpdGVfcGVyY2VudGlsZSA9IG50aWxlKGZhdm9yaXRlX2NvdW50LCAxMDApLAogICAgICAgICByZXR3ZWV0X3BlcmNlbnRpbGUgPSBudGlsZShyZXR3ZWV0X2NvdW50LCAxMDApLAogICAgICAgICByZXBseV9wZXJjZW50aWxlID0gbnRpbGUocmVwbHlfY291bnQsIDEwMCkpICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoYXZnX3BlcmNlbnRpbGUgPSByb3VuZChtZWFuKGModmlld19wZXJjZW50aWxlLCBmYXZvcml0ZV9wZXJjZW50aWxlLCByZXR3ZWV0X3BlcmNlbnRpbGUsIHJlcGx5X3BlcmNlbnRpbGUpLCBuYS5ybSA9IFRSVUUpLCAyKSkKCm5hX2NvdW50KCkKCmRhdGFfcGVyY2VudGlsZSA8LSBkYXRhX3Bvc3RzWywgYygiaWQiLCAidmlld19wZXJjZW50aWxlIiwgImZhdm9yaXRlX3BlcmNlbnRpbGUiLCAicmV0d2VldF9wZXJjZW50aWxlIiwgInJlcGx5X3BlcmNlbnRpbGUiLCAiYXZnX3BlcmNlbnRpbGUiKV0KCnByaW50KGRhdGFfcGVyY2VudGlsZSkKYGBgCgojIENhbGN1bGF0aW9uIG9mIHRoZSBtYXhpbXVtIG51bWJlciBvZiB2aWV3cyBmb3IgZWFjaCBIRUkKCmBgYHtyfQptYXhfdmlld19jb3VudHMgPC0gdGFwcGx5KGRhdGFfcG9zdHMkdmlld19jb3VudCwgZGF0YV9wb3N0cyRpZCwgbWF4LCBuYS5ybSA9IFRSVUUpCgpwcmludChtYXhfdmlld19jb3VudHMpCmBgYAoKIyBSZW1vdmFsIG9mIE5BcwoKYGBge3J9CiMgRnJvbSB2aWV3IGNvdW50CmRhdGFfcG9zdHMkdmlld19jb3VudCA8LSBpZmVsc2UoCiAgaXMubmEoZGF0YV9wb3N0cyR2aWV3X2NvdW50KSwKICByb3VuZChtYXhfdmlld19jb3VudHNbZGF0YV9wb3N0cyRpZF0gKiAoZGF0YV9wb3N0cyRhdmdfcGVyY2VudGlsZSAvIDEwMCkpLAogIGRhdGFfcG9zdHMkdmlld19jb3VudCkKCiMgRnJvbSB2aWV3IHBlcmNlbnRpbGUKZGF0YV9wb3N0cyR2aWV3X3BlcmNlbnRpbGUgPC0gaWZlbHNlKAogIGlzLm5hKGRhdGFfcG9zdHMkdmlld19wZXJjZW50aWxlKSwKICBkYXRhX3Bvc3RzJGF2Z19wZXJjZW50aWxlLAogIGRhdGFfcG9zdHMkdmlld19wZXJjZW50aWxlKQpgYGAKCiMgVmlzdWFsaXphdGlvbiBvZiBOQXMgaW4gYWxsIGNvbHVtbnMKCmBgYHtyfQpuYV9jb3VudCgpCmBgYAoKIyBGdW5jdGlvbiB0byBjYWxjdWxhdGUgYXZlcmFnZSBwb3N0cwoKYGBge3J9CmF2ZXJhZ2VfcG9zdHMgPC0gZnVuY3Rpb24odGltZWZyYW1lKXsKICAjIENhbGN1bGF0aW9uIG9mIHRoZSB0aW1lZnJhbWUgYmV0d2VlbiBlYXJsaWVzdCBhbmQgbGF0ZXN0IHBvc3QgZm9yIGVhY2ggSEVJCiAgZGF0ZV9yYW5nZSA8LSBkYXRhX3Bvc3RzICU+JQogICAgZ3JvdXBfYnkoaWQpICU+JQogICAgc3VtbWFyaXNlKG1pbl9kYXRlID0gbWluKGNyZWF0ZWRfYXQpLAogICAgICAgICAgICAgIG1heF9kYXRlID0gbWF4KGNyZWF0ZWRfYXQpKSAlPiUKICAgIG11dGF0ZShudW1fZGF5cyA9IGFzLm51bWVyaWMoZGlmZnRpbWUobWF4X2RhdGUsIG1pbl9kYXRlLCB1bml0cyA9IHRpbWVmcmFtZSkpKQogIAogICMgTmFtaW5nIHRoZSBjb2x1bW4gcmVzcGVjdGluZyB0aGUgdGltZWZyYW1lCiAgY29sdW1uX25hbWUgPC0gcGFzdGUwKCJhdmdfcG9zdHNfcGVyXyIsIHRpbWVmcmFtZSkKICAKICAjIENhbGN1bGF0aW9uIG9mIHRoZSBudW1iZXIgb2YgcG9zdHMgcGVyIGRheSBmb3IgZWFjaCBIRUkKICBwb3N0c19wZXJfdGltZWZyYW1lIDwtIG51bWJlcl9wb3N0cyAlPiUKICAgIGxlZnRfam9pbihkYXRlX3JhbmdlLCBieSA9ICJpZCIpICU+JQogICAgbXV0YXRlKCEhY29sdW1uX25hbWUgOj0gcm91bmQoKHBvc3RzIC8gbnVtX2RheXMpLCAyKSkKICAKICBwcmludChwb3N0c19wZXJfdGltZWZyYW1lKQogIHJldHVybihwb3N0c19wZXJfdGltZWZyYW1lKQp9CmBgYAoKYGBge3J9CnBvc3RzX3Blcl9kYXkgPC0gYXZlcmFnZV9wb3N0cygiZGF5cyIpCnBvc3RzX3Blcl93ZWVrIDwtIGF2ZXJhZ2VfcG9zdHMoIndlZWtzIikKYGBgCgojIFBsb3QgZm9yIHRoZSBhdmVyYWdlIG51bWJlciBvZiBwb3N0cyBwZXIgZGF5IGZvciBlYWNoIEhFSQoKYGBge3J9CmJhcnBsb3QocG9zdHNfcGVyX2RheSRhdmdfcG9zdHNfcGVyX2RheXMsCiAgICAgICAgbmFtZXMuYXJnID0gcG9zdHNfcGVyX2RheSRpZCwKICAgICAgICBtYWluID0gIkF2ZXJhZ2UgUG9zdHMgcGVyIERheSIsCiAgICAgICAgeGxhYiA9ICJIRUkiLAogICAgICAgIHlsYWIgPSAiQXZlcmFnZSBOdW1iZXIgb2YgUG9zdHMiLAogICAgICAgIHlsaW0gPSBjKDAsIG1heChwb3N0c19wZXJfZGF5JGF2Z19wb3N0c19wZXJfZGF5cykgKyAxKSwKICAgICAgICBsYXMgPSAyLAogICAgICAgIGNvbCA9ICIjMzQ5OERCIikKCiMgQWRkaW5nIHRleHQgbGFiZWxzIG92ZXIgZWFjaCBiYXIgYW5kIGFsaWduaW5nIGl0IHdpdGggdGhlIGNlbnRlciBvZiBlYWNoIGJhciAKdGV4dCh4ID0gYmFycGxvdChwb3N0c19wZXJfZGF5JGF2Z19wb3N0c19wZXJfZGF5cywgcGxvdCA9IEZBTFNFKSwKICAgICB5ID0gcG9zdHNfcGVyX2RheSRhdmdfcG9zdHNfcGVyX2RheXMsCiAgICAgbGFiZWxzID0gcm91bmQocG9zdHNfcGVyX2RheSRhdmdfcG9zdHNfcGVyX2RheXMsIDIpLAogICAgIHBvcyA9IDMpCmBgYAoKIyBQbG90IGZvciB0aGUgYXZlcmFnZSBudW1iZXIgb2YgcG9zdHMgcGVyIHdlZWsgZm9yIGVhY2ggSEVJCgpgYGB7cn0KYmFycGxvdChwb3N0c19wZXJfd2VlayRhdmdfcG9zdHNfcGVyX3dlZWtzLAogICAgICAgIG5hbWVzLmFyZyA9IHBvc3RzX3Blcl93ZWVrJGlkLAogICAgICAgIG1haW4gPSAiQXZlcmFnZSBQb3N0cyBwZXIgV2VlayIsCiAgICAgICAgeGxhYiA9ICJIRUkiLAogICAgICAgIHlsYWIgPSAiQXZlcmFnZSBOdW1iZXIgb2YgUG9zdHMiLAogICAgICAgIHlsaW0gPSBjKDAsIG1heChwb3N0c19wZXJfd2VlayRhdmdfcG9zdHNfcGVyX3dlZWtzKSArIDUpLAogICAgICAgIGxhcyA9IDIsCiAgICAgICAgY29sID0gIiNFNzRDM0MiKQoKdGV4dCh4ID0gYmFycGxvdChwb3N0c19wZXJfd2VlayRhdmdfcG9zdHNfcGVyX3dlZWtzLCBwbG90ID0gRkFMU0UpLAogICAgIHkgPSBwb3N0c19wZXJfd2VlayRhdmdfcG9zdHNfcGVyX3dlZWtzLAogICAgIGxhYmVscyA9IHJvdW5kKHBvc3RzX3Blcl93ZWVrJGF2Z19wb3N0c19wZXJfd2Vla3MsIDIpLAogICAgIHBvcyA9IDMpCmBgYAoKIyBEZWZpbmluZyB0aGUgaW50ZXJ2YWxzIG9mIHRpbWUgZm9yIHRoZSBhY2FkZW1pYyB5ZWFyCgpgYGB7cn0KaW50ZXJ2YWxzIDwtIGxpc3QoCiAgaW50ZXJ2YWwxID0gYXMuUE9TSVhjdChjKCIyMDIyLTA4LTMxIiwgIjIwMjItMTItMTUiKSksCiAgaW50ZXJ2YWwyID0gYXMuUE9TSVhjdChjKCIyMDIzLTAxLTA0IiwgIjIwMjMtMDQtMDEiKSksCiAgaW50ZXJ2YWwzID0gYXMuUE9TSVhjdChjKCIyMDIzLTA0LTE0IiwgIjIwMjMtMDYtMTUiKSkKKQpgYGAKCiMgRnVuY3Rpb24gdG8gY2hlY2sgaWYgYSBkYXRlIGZhbGxzIHdpdGhpbiBhIGdpdmVuIGludGVydmFsIG9mIHRpbWUgYW5kIGFwcGx5IGFwcHJvcHJpYXRlIEJvb2xlYW4KCmBgYHtyfQpjaGVja19pbnRlcnZhbCA8LSBmdW5jdGlvbihkYXRlKSB7CiAgZm9yIChpIGluIDE6bGVuZ3RoKGludGVydmFscykpIHsKICAgIGludGVydmFsX3N0YXJ0IDwtIGludGVydmFsc1tbaV1dWzFdCiAgICBpbnRlcnZhbF9lbmQgPC0gaW50ZXJ2YWxzW1tpXV1bMl0KICAgIGlmIChkYXRlID49IGludGVydmFsX3N0YXJ0ICYgZGF0ZSA8PSBpbnRlcnZhbF9lbmQpIHsKICAgICAgcmV0dXJuKFRSVUUpCiAgICB9CiAgfQogIHJldHVybihGQUxTRSkKfQpgYGAKCmBgYHtyfQpkYXRhX3Bvc3RzJGFjYWRlbWljX3llYXIgPC0gc2FwcGx5KGRhdGFfcG9zdHMkY3JlYXRlZF9hdCwgY2hlY2tfaW50ZXJ2YWwpCnByaW50KGRhdGEuZnJhbWUoaWQgPSBkYXRhX3Bvc3RzJGlkLCBhY2FkZW1pY195ZWFyID0gZGF0YV9wb3N0cyRhY2FkZW1pY195ZWFyKSkKYGBgCgojIEZ1bmN0aW9uIHRvIGNvdW50IG51bWJlciBvZiBwb3N0cyBhbmQgYXZlcmFnZSBwZXIgZGF5IGR1cmluZyBhY2FkZW1pYyB0aW1lIGFuZCB2YWNhdGlvbiB0aW1lCgpgYGB7cn0KYW5hbHl6ZV9wb3N0cyA8LSBmdW5jdGlvbihhY2FkZW1pY195ZWFyX2ZpbHRlcikgewogICMgRmlsdGVyaW5nIHRoZSBkYXRhIGJhc2VkIG9uIHRoZSBhY2FkZW1pY195ZWFyX2ZpbHRlcgogIGZpbHRlcmVkX2RhdGEgPC0gZGF0YV9wb3N0cyAlPiUKICAgIGZpbHRlcihhY2FkZW1pY195ZWFyID09IGFjYWRlbWljX3llYXJfZmlsdGVyKQogIAogICMgQ291bnQgb2YgZGF5cyBmb3IgZWFjaCBIRUkKICB1bmlxdWVfZGF5cyA8LSBmaWx0ZXJlZF9kYXRhICU+JQogICAgZ3JvdXBfYnkoaWQpICU+JQogICAgc3VtbWFyaXNlKHVuaXF1ZV9kYXlzID0gbl9kaXN0aW5jdChhcy5EYXRlKGNyZWF0ZWRfYXQpKSkKICAKICAjIENvdW50IG9mIHBvc3RzIGZvciBlYWNoIEhFSQogIG51bWJlcl9wb3N0c19ib29sZWFuIDwtIGZpbHRlcmVkX2RhdGEgJT4lCiAgICBncm91cF9ieShpZCkgJT4lCiAgICBzdW1tYXJpc2UoY291bnQgPSBuKCkpCiAgCiAgIyBOYW1pbmcgdGhlIGNvbHVtbiByZXNwZWN0aW5nIHRoZSB0aW1lIHBlcmlvZAogIHRpbWUgPC0gaWZlbHNlKGFjYWRlbWljX3llYXJfZmlsdGVyLCAiYWNhZGVtaWNfdGltZSIsICJ2YWNhdGlvbl90aW1lIikKICBjb2x1bW5fbmFtZSA8LSBwYXN0ZTAoImF2Z19wb3N0c19pbl8iLCB0aW1lKQogIAogICMgQ29tYmluYXRpb24gb2YgZGF0YSBhbmQgY2FsY3VsYXRpb24gb2YgYXZlcmFnZSBwb3N0cyBwZXIgZGF5CiAgY29tYmluZWRfZGF0YSA8LSBsZWZ0X2pvaW4odW5pcXVlX2RheXMsIG51bWJlcl9wb3N0c19ib29sZWFuLCBieSA9ICJpZCIpCiAgY29tYmluZWRfZGF0YSA8LSBjb21iaW5lZF9kYXRhICU+JQogICAgbXV0YXRlKCEhY29sdW1uX25hbWUgOj0gcm91bmQoKGNvdW50IC8gdW5pcXVlX2RheXMpLCAyKSkKICAKICBwcmludChjb21iaW5lZF9kYXRhKQogIHJldHVybihjb21iaW5lZF9kYXRhKQp9CmBgYAoKYGBge3J9CmRhdGFfcG9zdHNfYWNhZGVtaWMgPC0gYW5hbHl6ZV9wb3N0cyhUUlVFKQpkYXRhX3Bvc3RzX3ZhY2F0aW9ucyA8LSBhbmFseXplX3Bvc3RzKEZBTFNFKQpgYGAKCiMgUGxvdCBmb3IgdGhlIGF2ZXJhZ2UgbnVtYmVyIG9mIHBvc3RzIGR1cmluZyBhY2FkZW1pYyB0aW1lIGZvciBlYWNoIEhFSQoKYGBge3J9CmJhcnBsb3QoZGF0YV9wb3N0c19hY2FkZW1pYyRhdmdfcG9zdHNfaW5fYWNhZGVtaWNfdGltZSwKICAgICAgICBuYW1lcy5hcmcgPSBkYXRhX3Bvc3RzX2FjYWRlbWljJGlkLAogICAgICAgIG1haW4gPSAiQXZlcmFnZSBQb3N0cyBkdXJpbmcgQWNhZGVtaWMgVGltZSIsCiAgICAgICAgeGxhYiA9ICJIRUkiLAogICAgICAgIHlsYWIgPSAiQXZlcmFnZSBOdW1iZXIgb2YgUG9zdHMiLAogICAgICAgIHlsaW0gPSBjKDAsIG1heChkYXRhX3Bvc3RzX2FjYWRlbWljJGF2Z19wb3N0c19pbl9hY2FkZW1pY190aW1lKSArIDUpLAogICAgICAgIGxhcyA9IDIsCiAgICAgICAgY29sID0gIiMzNDQ5NUUiKQoKdGV4dCh4ID0gYmFycGxvdChkYXRhX3Bvc3RzX2FjYWRlbWljJGF2Z19wb3N0c19pbl9hY2FkZW1pY190aW1lLCBwbG90ID0gRkFMU0UpLAogICAgIHkgPSBkYXRhX3Bvc3RzX2FjYWRlbWljJGF2Z19wb3N0c19pbl9hY2FkZW1pY190aW1lLAogICAgIGxhYmVscyA9IHJvdW5kKGRhdGFfcG9zdHNfYWNhZGVtaWMkYXZnX3Bvc3RzX2luX2FjYWRlbWljX3RpbWUsIDIpLAogICAgIHBvcyA9IDMpCmBgYAoKIyBQbG90IGZvciB0aGUgYXZlcmFnZSBudW1iZXIgb2YgcG9zdHMgZHVyaW5nIHZhY2F0aW9uIHRpbWUgZm9yIGVhY2ggSEVJCgpgYGB7cn0KYmFycGxvdChkYXRhX3Bvc3RzX3ZhY2F0aW9ucyRhdmdfcG9zdHNfaW5fdmFjYXRpb25fdGltZSwKICAgICAgICBuYW1lcy5hcmcgPSBkYXRhX3Bvc3RzX3ZhY2F0aW9ucyRpZCwKICAgICAgICBtYWluID0gIkF2ZXJhZ2UgUG9zdHMgZHVyaW5nIFZhY2F0aW9uIFRpbWUiLAogICAgICAgIHhsYWIgPSAiSEVJIiwKICAgICAgICB5bGFiID0gIkF2ZXJhZ2UgTnVtYmVyIG9mIFBvc3RzIiwKICAgICAgICB5bGltID0gYygwLCBtYXgoZGF0YV9wb3N0c192YWNhdGlvbnMkYXZnX3Bvc3RzX2luX3ZhY2F0aW9uX3RpbWUpICsgNSksCiAgICAgICAgbGFzID0gMiwKICAgICAgICBjb2wgPSAiI0QzNTQwMCIpCgp0ZXh0KHggPSBiYXJwbG90KGRhdGFfcG9zdHNfdmFjYXRpb25zJGF2Z19wb3N0c19pbl92YWNhdGlvbl90aW1lLCBwbG90ID0gRkFMU0UpLAogICAgIHkgPSBkYXRhX3Bvc3RzX3ZhY2F0aW9ucyRhdmdfcG9zdHNfaW5fdmFjYXRpb25fdGltZSwKICAgICBsYWJlbHMgPSByb3VuZChkYXRhX3Bvc3RzX3ZhY2F0aW9ucyRhdmdfcG9zdHNfaW5fdmFjYXRpb25fdGltZSwgMiksCiAgICAgcG9zID0gMykKYGBgCgojIERhdGEgcHJlcGFyYXRpb24gZm9yIGRhdGVzIAoKYGBge3J9CiMgQ3JlYXRpbmcgbmV3IHRhYmxlIHRoYXQgY29udGFpbnMgYSBuZXcgY29sdW1uIGZvciB0aGUgZGF5IG9mIHRoZSB3ZWVrCmRhdGFfcG9zdHNfZGF5cyA8LSBkYXRhX3Bvc3RzICU+JQogIG11dGF0ZShkYXlfb2Zfd2VlayA9IHdlZWtkYXlzKGNyZWF0ZWRfYXQpKQoKIyBTZWxlY3Rpbmcgb25seSB0aGUgaWQsIGNyZWF0ZWRfYXQsIGFuZCBkYXlfb2Zfd2VlayBjb2x1bW5zIGZvciB0aGUgbmV3IHRhYmxlCmRhdGFfcG9zdHNfZGF5cyA8LSBkYXRhX3Bvc3RzX2RheXMgJT4lCiAgc2VsZWN0KGlkLCBjcmVhdGVkX2F0LCBkYXlfb2Zfd2VlaykKCiMgQ3JlYXRlIGNvbHVtbiBob3VyIGZyb20gY3JlYXRlZF9hdApkYXRhX3Bvc3RzX2RheXMkY3JlYXRlZF9ob3VyIDwtIGFzLm51bWVyaWMoZm9ybWF0KGRhdGFfcG9zdHNfZGF5cyRjcmVhdGVkX2F0LCAiJUgiKSkKCnByaW50KGRhdGFfcG9zdHNfZGF5cykKYGBgCgpgYGB7cn0KIyBHcm91cGluZyBieSBpZCBhbmQgZGF5X29mX3dlZWssIHRoZW4gY291bnRpbmcgdGhlIG51bWJlciBvZiBwb3N0cwpudW1iZXJfcG9zdHNfZGF5cyA8LSBkYXRhX3Bvc3RzX2RheXMgJT4lCiAgZ3JvdXBfYnkoaWQsIGRheV9vZl93ZWVrKSAlPiUKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpCgojIEdyb3VwaW5nIGJ5IGlkLCBkYXlfb2Zfd2VlayBhbmQgZGF5IGNyZWF0ZWQgYXQsIHRoZW4gY291bnRpbmcgdGggZW51bWJlciBvZiB0d2VldHMKbnVtYmVyX3Bvc3RzX3Blcl9kYXkgPC0gZGF0YV9wb3N0c19kYXlzICU+JQogICAgbXV0YXRlKGNyZWF0ZWRfZGF0ZSA9IGFzLkRhdGUoY3JlYXRlZF9hdCkpICU+JSAKICAgIGdyb3VwX2J5KGlkLCBkYXlfb2Zfd2VlaywgY3JlYXRlZF9kYXRlKSAlPiUKICAgIHN1bW1hcml6ZShjb3VudCA9IG4oKSkKCiMgRmluZGluZyBmb3IgZWFjaCBIRUkgdGhlIGF2ZXJhZ2UgY291bnQgb2YgcG9zdHMgcGVyIGRheQphdmVyYWdlX251bWJlcl9wb3N0c19wZXJfZGF5IDwtIG51bWJlcl9wb3N0c19wZXJfZGF5ICU+JQogIGdyb3VwX2J5KGlkLCBkYXlfb2Zfd2VlaykgJT4lCiAgc3VtbWFyaXNlKGF2ZXJhZ2VfY291bnQgPSByb3VuZChtZWFuKGNvdW50KSwgMikpCgpwcmludChudW1iZXJfcG9zdHNfZGF5cykKYGBgCgojIEhpZ2hlc3QgYW5kIGxvd2VzdCBwb3N0cwoKYGBge3J9CiMgRmluZGluZyB0aGUgSEVJIHdpdGggdGhlIGxvd2VzdCBjb3VudCBvZiBwb3N0cyBwZXIgZGF5Cmxvd2VzdF9jb3VudCA8LSBudW1iZXJfcG9zdHNfZGF5cyAlPiUKICBncm91cF9ieShkYXlfb2Zfd2VlaykgJT4lCiAgc2xpY2VfbWluKG9yZGVyX2J5ID0gY291bnQpICU+JQogIHNlbGVjdChkYXlfb2Zfd2VlaywgaWQsIGNvdW50KQoKIyBGaW5kaW5nIHRoZSBIRUkgd2l0aCB0aGUgaGlnaGVzdCBjb3VudCBvZiBwb3N0cyBwZXIgZGF5CmhpZ2hlc3RfY291bnQgPC0gbnVtYmVyX3Bvc3RzX2RheXMgJT4lCiAgZ3JvdXBfYnkoZGF5X29mX3dlZWspICU+JQogIHNsaWNlX21heChvcmRlcl9ieSA9IGNvdW50KSAlPiUKICBzZWxlY3QoZGF5X29mX3dlZWssIGlkLCBjb3VudCkKCiMgQ29tYmluZSB0aGUgcmVzdWx0cwpoaWdoX2xvd19IRUkgPC0gYmluZF9yb3dzKGxvd2VzdF9jb3VudCwgaGlnaGVzdF9jb3VudCkgJT4lCiAgYXJyYW5nZShkYXlfb2Zfd2VlaykKCnByaW50KGhpZ2hfbG93X0hFSSkKYGBgCgojIFBsb3QgZm9yIHRoZSBoaWdoZXN0IGFuZCBsb3dlc3QgY291bnQgb2YgcG9zdHMgcGVyIGRheSBmb3IgZWFjaCBkYXkgb2YgdGhlIHdlZWsKCmBgYHtyfQpnZ3Bsb3QoaGlnaF9sb3dfSEVJLCBhZXMoeCA9IGRheV9vZl93ZWVrLCB5ID0gY291bnQsIGZpbGwgPSBpZCkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGNvdW50KSwKICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuOSksCiAgICAgICAgICAgIHZqdXN0ID0gLTAuNSwKICAgICAgICAgICAgc2l6ZSA9IDMpICsKICBsYWJzKHRpdGxlID0gIkhpZ2hlc3QgYW5kIExvd2VzdCBDb3VudCBvZiBQb3N0cyBwZXIgRGF5IGZvciBFYWNoIERheSBvZiB0aGUgV2VlayIsCiAgICAgICB4ID0gIkRheSBvZiB0aGUgV2VlayIsIHkgPSAiQ291bnQiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcmFpbmJvdyhsZW5ndGgodW5pcXVlKGhpZ2hfbG93X0hFSSRpZCkpKSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCiMgQXZlcmFnZSBvZiBwb3N0cwoKYGBge3J9CiMgRmluZGluZyB0aGUgSEVJIHdpdGggbG93ZXN0IGFuZCBoaWdoZXN0IGF2ZXJhZ2VkIGNvdW50IG9mIHBvc3RzIHBlciBkYXkKaGlnaF9sb3dfYXZlcmFnZV9IRUlzIDwtIGF2ZXJhZ2VfbnVtYmVyX3Bvc3RzX3Blcl9kYXkgJT4lCiAgZ3JvdXBfYnkoZGF5X29mX3dlZWspICU+JQogIGZpbHRlcihhdmVyYWdlX2NvdW50ID09IG1heChhdmVyYWdlX2NvdW50KSB8IGF2ZXJhZ2VfY291bnQgPT0gbWluKGF2ZXJhZ2VfY291bnQpKSAlPiUKICBhcnJhbmdlKGRheV9vZl93ZWVrLCBpZmVsc2UoYXZlcmFnZV9jb3VudCA9PSBtaW4oYXZlcmFnZV9jb3VudCksIGF2ZXJhZ2VfY291bnQsIC1hdmVyYWdlX2NvdW50KSkKCnByaW50KGhpZ2hfbG93X2F2ZXJhZ2VfSEVJcykKYGBgCgojIFBsb3QgZm9yIHRoZSBoaWdoZXN0IGFuZCBsb3dlc3QgYXZlcmFnZSBjb3VudCBvZiBwb3N0cyBwZXIgZGF5IGZvciBlYWNoIGRheSBvZiB0aGUgd2VlawoKYGBge3J9CmdncGxvdChoaWdoX2xvd19hdmVyYWdlX0hFSXMsIGFlcyh4ID0gZGF5X29mX3dlZWssIHkgPSBhdmVyYWdlX2NvdW50LCBmaWxsID0gaWQpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChhdmVyYWdlX2NvdW50LCAyKSksCiAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjcpLAogICAgICAgICAgICB2anVzdCA9IC0wLjUsCiAgICAgICAgICAgIHNpemUgPSAzKSArCiAgbGFicyh0aXRsZSA9ICJIaWdoZXN0IGFuZCBMb3dlc3QgQXZlcmFnZSBDb3VudCBvZiBQb3N0cyBwZXIgRGF5IGZvciBFYWNoIERheSBvZiB0aGUgV2VlayIsCiAgICAgICB4ID0gIkRheSBvZiB0aGUgV2VlayIsIHkgPSAiQXZlcmFnZSBDb3VudCIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSByYWluYm93KGxlbmd0aCh1bmlxdWUoaGlnaF9sb3dfSEVJJGlkKSkpKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKIyBGYXZvdXJpdGUgaG91ciBhbmQgZGF5CgpgYGB7cn0KZmF2b3VyaXRlX2RheV9oZWkgPC0gbnVtYmVyX3Bvc3RzX2RheXMgJT4lCiAgZ3JvdXBfYnkoaWQpICU+JQogIHRvcF9uKDEsIGNvdW50KSAlPiUKICBhcnJhbmdlKGlkKQoKcHJpbnQoZmF2b3VyaXRlX2RheV9oZWkpCmBgYAoKYGBge3J9Cm51bWJlcl9wb3N0c19ob3VycyA8LSBkYXRhX3Bvc3RzX2RheXMgJT4lCiAgZ3JvdXBfYnkoaWQsIGNyZWF0ZWRfaG91cikgJT4lCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUKICB1bmdyb3VwKCkKCmZhdm91cml0ZV9ob3VyX2hlaSA8LSBudW1iZXJfcG9zdHNfaG91cnMgJT4lCiAgZ3JvdXBfYnkoaWQpICU+JQogIHRvcF9uKDEsIGNvdW50KSAlPiUKICBhcnJhbmdlKGlkKQoKcHJpbnQoZmF2b3VyaXRlX2hvdXJfaGVpKQpgYGAKCiMgSGVhdG1hcHMKCiMgRnVuY3Rpb24gdG8gcGxvdCBoZWF0bWFwIGZvciB2YXJpb3VzIEhFSXMKCmBgYHtyfQpoZWF0bWFwX21ha2VyIDwtIGZ1bmN0aW9uKHRhcmdldF9pZCl7CiAgIyBGaWx0ZXJpbmcgZGF0YSBmb3IgdGhlIHNwZWNpZmljIEhFSQogIHRhcmdldF9kYXRhIDwtIGRhdGFfcG9zdHNfZGF5cyAlPiUKICAgIGZpbHRlcihpZCA9PSB0YXJnZXRfaWQpCiAgCiAgIyBHcm91cGluZyBieSBkYXkgb2YgdGhlIHdlZWsgYW5kIGhvdXIsIGFuZCBjb3VudGluZyB0aGUgbnVtYmVyIG9mIHR3ZWV0cwogIHR3ZWV0X2NvdW50cyA8LSB0YXJnZXRfZGF0YSAlPiUKICAgIGdyb3VwX2J5KGRheV9vZl93ZWVrLCBjcmVhdGVkX2hvdXIpICU+JQogICAgc3VtbWFyaXNlKG51bV9wb3N0cyA9IG4oKSkKICAKICAjIFBsb3R0aW5nIGhlYXRtYXAKICBnZ3Bsb3QodHdlZXRfY291bnRzLCBhZXMoeCA9IGRheV9vZl93ZWVrLCB5ID0gY3JlYXRlZF9ob3VyLCBmaWxsID0gbnVtX3Bvc3RzKSkgKwogICAgZ2VvbV90aWxlKCkgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAid2hpdGUiLCBoaWdoID0gImJsdWUiKSArCiAgICBsYWJzKHRpdGxlID0gcGFzdGUoIlBvc3QgSGVhdG1hcCBmb3IiLCB0YXJnZXRfaWQpLAogICAgICAgICB4ID0gIkRheSBvZiB0aGUgd2VlayIsCiAgICAgICAgIHkgPSAiSG91ciBvZiB0aGUgZGF5IikKfQpgYGAKCiMgUGxvdCBvZiBoZWF0bWFwIGZvciBlYWNoIEhFSQoKYGBge3J9CmhlYXRtYXBfbWFrZXIoImR1a2UuY3N2IikKaGVhdG1hcF9tYWtlcigiZXBmbC5jc3YiKQpoZWF0bWFwX21ha2VyKCJnb2UuY3N2IikKaGVhdG1hcF9tYWtlcigiaGFydmFyZC5jc3YiKQpoZWF0bWFwX21ha2VyKCJsZWljZXN0ZXIuY3N2IikKaGVhdG1hcF9tYWtlcigibWFuY2hlc3Rlci5jc3YiKQpoZWF0bWFwX21ha2VyKCJtaXQuY3N2IikKaGVhdG1hcF9tYWtlcigic2IuY3N2IikKaGVhdG1hcF9tYWtlcigic3RhbmZvcmQuY3N2IikKaGVhdG1hcF9tYWtlcigidHJpbml0eS5jc3YiKQpoZWF0bWFwX21ha2VyKCJ3di5jc3YiKQpoZWF0bWFwX21ha2VyKCJ5YWxlLmNzdiIpCmBgYAoKIyBIYXNodGFncwoKYGBge3J9CiMgVHJhbnNmb3JtaW5nIGVtcHR5IHN0cmluZ3MgaW50byBOQQpkYXRhX3Bvc3RzJGhhc2h0YWdzW2RhdGFfcG9zdHMkaGFzaHRhZ3MgPT0gIiJdIDwtIE5BCgojIFRhYmxlIHdpdGggbnVtYmVyIG9mIHVuaXF1ZSBoYXNodGFncyBhbmQgcGVyY2VudGFnZSBvZiB1c2FnZQpoYXNodGFncyA8LSBkYXRhX3Bvc3RzICU+JQogICAgICAgICAgICAgICAgZ3JvdXBfYnkoaWQpICU+JQogICAgICAgICAgICAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpLAogICAgICAgICAgICAgICAgICAgICAgICAgIG5hID0gc3VtKGlzLm5hKGhhc2h0YWdzKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgdW5pcXVlX2hhc2h0YWdzID0gbGVuZ3RoKHVuaXF1ZShoYXNodGFncykpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGhhc2h0YWdfcGVyY2VudGFnZSA9IHJvdW5kKCgoY291bnQgLSBuYSkgLyBjb3VudCAqIDEwMCksIDIpKQoKcHJpbnQoaGFzaHRhZ3MpCmBgYAoKIyBQbG90IGZvciB0aGUgY291bnQgb2YgdW5pcXVlIGhhc2h0YWdzIGZvciBlYWNoIEhFSQoKYGBge3J9CmJhcnBsb3QoaGFzaHRhZ3MkdW5pcXVlX2hhc2h0YWdzLAogICAgICAgIG5hbWVzLmFyZyA9IGhhc2h0YWdzJGlkLAogICAgICAgIG1haW4gPSAiVW5pcXVlIEhhc2h0YWdzIGZvciBFYWNoIEhFSSIsCiAgICAgICAgeGxhYiA9ICJIRUkiLAogICAgICAgIHlsYWIgPSAiQ291bnQgb2YgVW5pcXVlIEhhc2h0YWdzIiwKICAgICAgICB5bGltID0gYygwLCBtYXgoaGFzaHRhZ3MkdW5pcXVlX2hhc2h0YWdzKSArIDUwKSwKICAgICAgICBsYXMgPSAyLAogICAgICAgIGNvbD0gIiMxNkEwODUiKQoKdGV4dCh4ID0gYmFycGxvdChoYXNodGFncyR1bmlxdWVfaGFzaHRhZ3MsIHBsb3QgPSBGQUxTRSksCiAgICAgeSA9IGhhc2h0YWdzJHVuaXF1ZV9oYXNodGFncywKICAgICBsYWJlbHMgPSByb3VuZChoYXNodGFncyR1bmlxdWVfaGFzaHRhZ3MsIDIpLAogICAgIHBvcyA9IDMpCmBgYAoKIyBQbG90IGZvciB0aGUgdXNhZ2Ugb2YgaGFzaHRhZyBmb3IgZWFjaCBIRUkKCmBgYHtyfQpiYXJwbG90KGhhc2h0YWdzJGhhc2h0YWdfcGVyY2VudGFnZSwKICAgICAgICBuYW1lcy5hcmcgPSBoYXNodGFncyRpZCwKICAgICAgICBtYWluID0gIkhhc2h0YWdzIFBlcmNlbnRhZ2UgZm9yIEVhY2ggSEVJIiwKICAgICAgICB4bGFiID0gIkhFSSIsCiAgICAgICAgeWxhYiA9ICJIYXNodGFncyBQZXJjZW50YWdlIiwKICAgICAgICB5bGltID0gYygwLCBtYXgoaGFzaHRhZ3MkaGFzaHRhZ19wZXJjZW50YWdlKSArIDMwKSwKICAgICAgICBsYXMgPSAyLAogICAgICAgIGNvbD0gIiNGMUM0MEYiKQoKdGV4dCh4ID0gYmFycGxvdChoYXNodGFncyRoYXNodGFnX3BlcmNlbnRhZ2UsIHBsb3QgPSBGQUxTRSksCiAgICAgeSA9IGhhc2h0YWdzJGhhc2h0YWdfcGVyY2VudGFnZSwKICAgICBsYWJlbHMgPSByb3VuZChoYXNodGFncyRoYXNodGFnX3BlcmNlbnRhZ2UsIDIpLAogICAgIHBvcyA9IDMpCmBgYAoKIyBVUkwgdXNhZ2UKCmBgYHtyfQojIFRyYW5zZm9ybWluZyBlbXB0eSBzdHJpbmdzIGludG8gTkEKZGF0YV9wb3N0cyR1cmxzW2RhdGFfcG9zdHMkdXJscyA9PSAiIl0gPC0gTkEKCiMgVGFibGUgd2l0aCBudW1iZXIgb2YgcG9zdCwgbnVtYmVyIG9mIE5BIGFuZCB1cmwgcGVyY2VudGFnZSBvZiB1c2FnZQp1cmxfdXNhZ2UgPC0gZGF0YV9wb3N0cyAlPiUKICAgICAgICAgICAgICAgIGdyb3VwX2J5KGlkKSAlPiUKICAgICAgICAgICAgICAgIHN1bW1hcmlzZShjb3VudCA9IG4oKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBuYSA9IHN1bShpcy5uYSh1cmxzKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgdXJsX3BlcmNlbnRhZ2UgPSByb3VuZCgoKGNvdW50IC0gbmEpIC8gY291bnQgKiAxMDApLCAyKSkKCnByaW50KHVybF91c2FnZSkKYGBgCgojIFBsb3QgZm9yIHRoZSB1c2FnZSBvZiBoYXNodGFnIGZvciBlYWNoIEhFSQoKYGBge3J9CmJhcnBsb3QodXJsX3VzYWdlJHVybF9wZXJjZW50YWdlLAogICAgICAgIG5hbWVzLmFyZyA9IHVybF91c2FnZSRpZCwKICAgICAgICBtYWluID0gIlVybHMgUGVyY2VudGFnZSBmb3IgRWFjaCBIRUkiLAogICAgICAgIHhsYWIgPSAiSEVJIiwKICAgICAgICB5bGFiID0gIlVybHMgUGVyY2VudGFnZSIsCiAgICAgICAgeWxpbSA9IGMoMCwgbWF4KHVybF91c2FnZSR1cmxfcGVyY2VudGFnZSkgKyAxMCksCiAgICAgICAgbGFzID0gMiwKICAgICAgICBjb2w9ICIjOEU0NEFEIikKCnRleHQoeCA9IGJhcnBsb3QodXJsX3VzYWdlJHVybF9wZXJjZW50YWdlLCBwbG90ID0gRkFMU0UpLAogICAgIHkgPSB1cmxfdXNhZ2UkdXJsX3BlcmNlbnRhZ2UsCiAgICAgbGFiZWxzID0gcm91bmQodXJsX3VzYWdlJHVybF9wZXJjZW50YWdlLCAyKSwKICAgICBwb3MgPSAzKQpgYGAKCiMgVGV4dAoKYGBge3J9CmRhdGFfcG9zdHNfY29udGVudCA8LSBkYXRhX3Bvc3RzICU+JQogICAgICAgICAgICBzZWxlY3QoaWQsIHRleHQpCgojIENvdW50aW5nIG51bWJlciBvZiB3b3JkcwpkYXRhX3Bvc3RzX2NvbnRlbnQgPC0gZGF0YV9wb3N0c19jb250ZW50ICU+JQogIG11dGF0ZShudW1fd29yZHMgPSBsZW5ndGhzKHN0cnNwbGl0KHRleHQsICJcXHMrIikpKQoKIyBHcm91cGluZyBieSBIRUkgYW5kIGNhbGN1bGF0ZSBhdmVyYWdlLCBtaW5pbXVtLCBhbmQgbWF4aW11bSB2YWx1ZXMgb2YgbnVtYmVyIG9mIHdvcmRzCmRhdGFfcG9zdHNfY29udGVudF9tZXRyaWNzIDwtIGRhdGFfcG9zdHNfY29udGVudCAlPiUKICBncm91cF9ieShpZCkgJT4lCiAgc3VtbWFyaXNlKGF2ZXJhZ2VfbnVtX3dvcmRzID0gbWVhbihudW1fd29yZHMpLAogICAgICAgICAgICBtaW5fbnVtX3dvcmRzID0gbWluKG51bV93b3JkcyksCiAgICAgICAgICAgIG1heF9udW1fd29yZHMgPSBtYXgobnVtX3dvcmRzKSkKcHJpbnQoZGF0YV9wb3N0c19jb250ZW50X21ldHJpY3MpCmBgYAoKIyBQbG90IGZvciB0aGUgYXZlcmFnZSwgbWF4aW11bSBhbmQgbWluaW11bSB2YWx1ZXMgb2Ygd29yZHMgZm9yIGVhY2ggSEVJCgpgYGB7cn0KZ2dwbG90KGRhdGFfcG9zdHNfY29udGVudF9tZXRyaWNzLCBhZXMoeCA9IGlkLCB5ID0gYXZlcmFnZV9udW1fd29yZHMpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSAiQXZlcmFnZSIpKSArCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IG1pbl9udW1fd29yZHMsIHltYXggPSBtYXhfbnVtX3dvcmRzLCBjb2xvciA9ICJSYW5nZSIpLCB3aWR0aCA9IDAuMikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJBdmVyYWdlIiA9ICIjMTk3NkQyIiwgIlJhbmdlIiA9ICIjRUY1MzUwIikpICsKICBsYWJzKHRpdGxlID0gIldvcmQgQ291bnQgU3VtbWFyeSBieSBIRUkiLAogICAgICAgeCA9ICJIRUkiLAogICAgICAgeSA9ICJOdW1iZXIgb2YgV29yZHMiLAogICAgICAgY29sb3IgPSAiTWV0cmljIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0ID0gMSkpCmBgYAoKIyBDbHVzdGVycwoKYGBge3J9CiMgQ3JlYXRpbmcgdGFibGUgZm9yIGNsdXN0ZXIgYWxnb3JpdGhtcwoKIyBKb2luaW5nIGF0dHJpYnV0ZSBwZXJjZW50YWdlX3R3ZWV0cyAocGVyY2VudGFnZSBvZiB0d2VldHMgb3V0IG9mIGFsbCBwb3N0cykgYW5kIHBlcmNlbnRhZ2VfcmVwbGllcyAocGVyY2VudGFnZSBvZiByZXBsaWVzIG91dCBvZiBhbGwgcG9zdHMpIGZyb20gbnVtYmVyX3Bvc3RzIGFsc28gYWRkaW5nIHVuaXF1ZV9oYXNodGFncyAobnVtYmVyIG9mIHVuaXF1ZSBoYXNodGFncykgYW5kIGhhc2h0YWdfcGVyY2VudGFnZSAocGVyY2VudGFnZSBvZiBwb3N0cyB0aGF0IGNvbnRhaW4gYSBoYXNodGFnKSBmcm9tIGhhc2h0YWdzLCBwZXIgSEVJCmNsdXN0ZXJfdGFibGUgPC0gbWVyZ2Uoc2VsZWN0KGhhc2h0YWdzLCBpZCwgdW5pcXVlX2hhc2h0YWdzLCBoYXNodGFnX3BlcmNlbnRhZ2UpLCBzZWxlY3QoZGF0YV9yYXRpbywgaWQsIHBlcmNlbnRhZ2VfdHdlZXRzLCBwZXJjZW50YWdlX3JlcGxpZXMpLCBieSA9ICJpZCIsIGFsbD1UUlVFKQoKIyBKb2luaW5nIGF0dHJpYnV0ZSBhdmdfcG9zdHNfcGVyX2RheXMgKGF2ZXJhZ2Ugb2YgcG9zdHMgcGVyIGRheSkgZnJvbSBwb3N0c19wZXJfZGF5IHBlciBIRUkKY2x1c3Rlcl90YWJsZSA8LSBtZXJnZShjbHVzdGVyX3RhYmxlLCBzZWxlY3QocG9zdHNfcGVyX2RheSwgaWQsIGF2Z19wb3N0c19wZXJfZGF5cyksIGJ5ID0gImlkIiwgYWxsPVRSVUUpCgojIEpvaW5pbmcgYXR0cmlidXRlIGF2Z19wb3N0c19wZXJfd2Vla3MgKGF2ZXJhZ2Ugb2YgcG9zdHMgcGVyIHdlZWspIGZyb20gcG9zdHNfcGVyX3dlZWsgcGVyIEhFSQpjbHVzdGVyX3RhYmxlIDwtIG1lcmdlKGNsdXN0ZXJfdGFibGUsIHNlbGVjdChwb3N0c19wZXJfd2VlaywgaWQsIGF2Z19wb3N0c19wZXJfd2Vla3MpLCBieSA9ICJpZCIsIGFsbD1UUlVFKQoKIyBKb2luaW5nIGF0dHJpYnV0ZSBhdmdfcG9zdHNfaW5fYWNhZGVtaWNfdGltZSAoYXZlcmFnZSBvZiBwb3N0cyBkdXJpbmcgYWNhZGVtaWMgdGltZSkgIGZyb20gZGF0YV9wb3N0c19hY2FkZW1pYyBwZXIgSEVJCmNsdXN0ZXJfdGFibGUgPC0gbWVyZ2UoY2x1c3Rlcl90YWJsZSwgc2VsZWN0KGRhdGFfcG9zdHNfYWNhZGVtaWMsIGlkLCBhdmdfcG9zdHNfaW5fYWNhZGVtaWNfdGltZSksIGJ5ID0gImlkIiwgYWxsPVRSVUUpCgojIEpvaW5pbmcgYXR0cmlidXRlIGF2Z19wb3N0c19pbl92YWNhdGlvbl90aW1lIChhdmVyYWdlIG9mIHBvc3RzIGR1cmluZyB2YWNhdGlvbiB0aW1lKSBmcm9tIGRhdGFfcG9zdHNfdmFjYXRpb25zIHBlciBIRUkKY2x1c3Rlcl90YWJsZSA8LSBtZXJnZShjbHVzdGVyX3RhYmxlLCBzZWxlY3QoZGF0YV9wb3N0c192YWNhdGlvbnMsIGlkLCBhdmdfcG9zdHNfaW5fdmFjYXRpb25fdGltZSksIGJ5ID0gImlkIiwgYWxsPVRSVUUpCgojIEpvaW5pbmcgYXR0cmlidXRlIGNyZWF0ZWRfaG91ciAoaG91ciB3aGVyZSBldmVyeSBIRUkgbWFkZSBtb3JlIHBvc3RzKSBmcm9tIGZhdm91cml0ZV9ob3VyX2hlaSBwZXIgSEVJCmNsdXN0ZXJfdGFibGUgPC0gbWVyZ2UoY2x1c3Rlcl90YWJsZSwgc2VsZWN0KGZhdm91cml0ZV9ob3VyX2hlaSwgaWQsIGNyZWF0ZWRfaG91ciksIGJ5ID0gImlkIiwgYWxsPVRSVUUpCgojIEpvaW5pbmcgYXR0cmlidXRlIHVybF9wZXJjZW50YWdlIChwZXJjZW50YWdlIG9mIHBvc3RzIHRoYXQgY29udGFpbiBhbiB1cmwpIGZyb20gdXJsX3VzYWdlIHBlciBIRUkKY2x1c3Rlcl90YWJsZSA8LSBtZXJnZShjbHVzdGVyX3RhYmxlLCBzZWxlY3QodXJsX3VzYWdlLCBpZCwgdXJsX3BlcmNlbnRhZ2UpLCBieSA9ICJpZCIsIGFsbD1UUlVFKQoKIyBKb2luaW5nIGF0dHJpYnV0ZSBhdmVyYWdlX251bV93b3JkcyAoYXZlcmFnZSBudW1iZXIgb2Ygd29yZHMgaW4gdGhlIHBvc3RzKSBmcm9tIGRhdGFfcG9zdHNfY29udGVudF9tZXRyaWNzIHBlciBIRUkKY2x1c3Rlcl90YWJsZSA8LSBtZXJnZShjbHVzdGVyX3RhYmxlLCBzZWxlY3QoZGF0YV9wb3N0c19jb250ZW50X21ldHJpY3MsIGlkLCBhdmVyYWdlX251bV93b3JkcyksIGJ5ID0gImlkIiwgYWxsPVRSVUUpCgpwcmludChjbHVzdGVyX3RhYmxlKQpgYGAKCiMgRnVuY3Rpb24gZm9yIGNsdXN0ZXIgbWV0aG9kCgpgYGB7cn0KY2x1c3Rlcl9tYWtlciA8LSBmdW5jdGlvbihudW1fY2x1c3RlcnMsIHRhYmxlKXsKICAjIEV4Y2x1ZGluZyBpZCBjb2x1bW4gZm9yIGNsdXN0ZXJpbmcKICBjbHVzdGVyX2RhdGEgPC0gc2VsZWN0KHRhYmxlLCAtaWQpCiAgICAKICAjIFNjYWxpbmcgdGhlIGRhdGEgZm9yIGttZWFucyBtZXRob2QKICBzY2FsZWRfZGF0YSA8LSBzY2FsZShjbHVzdGVyX2RhdGEpCiAgCiAga21lYW5zX21vZGVsIDwtIGttZWFucyhzY2FsZWRfZGF0YSwgY2VudGVycyA9IG51bV9jbHVzdGVycywgbnN0YXJ0ID0gMTApCgogICMgRXh0cmFjdCBjbHVzdGVyIGFzc2lnbm1lbnRzCiAgY2x1c3Rlcl9hc3NpZ25tZW50cyA8LSBrbWVhbnNfbW9kZWwkY2x1c3RlcgogIAogICMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBjb21iaW5pbmcgb3JpZ2luYWwgZGF0YSB3aXRoIGNsdXN0ZXIgYXNzaWdubWVudHMKICBjbHVzdGVyZWRfZGF0YSA8LSBjYmluZChjbHVzdGVyX3RhYmxlJGlkLCBjbHVzdGVyX2RhdGEsIGNsdXN0ZXIgPSBjbHVzdGVyX2Fzc2lnbm1lbnRzKQogIAogIGNsdXN0ZXJlZF9kYXRhIDwtIGNsdXN0ZXJlZF9kYXRhWywgYygiY2x1c3Rlcl90YWJsZSRpZCIsICJjbHVzdGVyIildCiAgCiAgcHJpbnQoY2x1c3RlcmVkX2RhdGEpCn0KYGBgCgojIEZ1bmN0aW9uIHRvIGRpc2NvdmVyIGJlc3QgbnVtYmVyIG9mIGNsdXN0ZXJzCgpgYGB7cn0KZWxib3dfbWFrZXIgPC0gZnVuY3Rpb24odGFibGUpewogIGNsdXN0ZXJfZGF0YSA8LSBzZWxlY3QodGFibGUsIC1pZCkKICBzY2FsZWRfZGF0YSA8LSBzY2FsZShjbHVzdGVyX2RhdGEpCiAgCiAgd3NzIDwtIHZlY3RvcigpCiAgcmFuZ2UgPC0gMToxMAogIAogIGZvciAoayBpbiByYW5nZSkgewogICAga21lYW5zX21vZGVsIDwtIGttZWFucyhzY2FsZWRfZGF0YSwgY2VudGVycyA9IGssIG5zdGFydCA9IDEwKQogICAgd3NzW2tdIDwtIGttZWFuc19tb2RlbCR0b3Qud2l0aGluc3MKICB9CiAgCiAgZWxib3dfZGYgPC0gZGF0YS5mcmFtZShrID0gcmFuZ2UsIFdTUyA9IHdzcykKICBnZ3Bsb3QoZWxib3dfZGYsIGFlcyh4ID0gaywgeSA9IFdTUykpICsKICAgIGdlb21fbGluZSgpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBsYWJzKHggPSAiTnVtYmVyIG9mIENsdXN0ZXJzIiwgeSA9ICJXaXRoaW4tQ2x1c3RlciBTdW0gb2YgU3F1YXJlcyAoV0NTUykiLAogICAgICAgICB0aXRsZSA9ICJFbGJvdyBNZXRob2QgZm9yIE9wdGltYWwgayIpICsKICAgIHRoZW1lX21pbmltYWwoKQp9CmBgYAoKIyBQbG90IG9mIEVsYm93IE1ldGhvZCBhbmQgc2VsZWN0aW9uIG9mIGJlc3QgbnVtYmVyIG9mIGNsdXN0ZXIgdG8gdmlldyBob3cgSEVJcyBhcmUgZ3JvdXBlZAoKYGBge3J9CmVsYm93X21ha2VyKGNsdXN0ZXJfdGFibGUpCmNsdXN0ZXJfbWFrZXIoMywgY2x1c3Rlcl90YWJsZSkKYGBgCgojIDIgUGFydGUKCgpgYGB7cn0KY2xlYW51cCA8LSBmdW5jdGlvbihkb2NzLCBzcGVjLndvcmRzPU5VTEwpewogICMgbG93ZXJjYXNlCiAgZG9jcyA8LSB0bV9tYXAoZG9jcywgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkKICAjIHJtIG51bWJlcnMKICBkb2NzIDwtIHRtX21hcChkb2NzLCByZW1vdmVOdW1iZXJzKQogICMgcm0gZW5nbGlzaCBjb21tb24gc3RvcFdvcmRzCiAgZG9jcyA8LSB0bV9tYXAoZG9jcywgcmVtb3ZlV29yZHMsIHN0b3B3b3JkcygiZW5nbGlzaCIpKQogICMgaWYgc3RvcHdvcmRzIGFyZSBzcGVjaWZpZWQgYXMgYSBjaGFyYWN0ZXIgdmVjdG9yCiAgaWYoIWlzLm51bGwoc3BlYy53b3JkcykpCiAgICBkb2NzIDwtIHRtX21hcChkb2NzLCByZW1vdmVXb3Jkcywgc3BlYy53b3JkcykKICAjIHJtIHB1bmN0dWF0aW9ucwogIGRvY3MgPC0gdG1fbWFwKGRvY3MsIHJlbW92ZVB1bmN0dWF0aW9uKQogICMgcm0gZXh0cmEgd2hpdGUgc3BhY2VzCiAgZG9jcyA8LSB0bV9tYXAoZG9jcywgc3RyaXBXaGl0ZXNwYWNlKQogICMgbGVtbWF0aXppbmcgdGV4dAogIGRvY3MgPC0gdG1fbWFwKGRvY3MsIGxlbW1hdGl6ZV93b3JkcykKICAKICBkb2NzCn0KYGBgCgpgYGB7cn0KcGFpcmluZyA8LSBmdW5jdGlvbih3b3JkX2RpY3QsIHBhaXJlZF93b3JkcykgewogIGlmIChucm93KHdvcmRfZGljdCkgIT0gbGVuZ3RoKHBhaXJlZF93b3JkcykpIHsKICAgIHN0b3AoIlRoZSBudW1iZXIgb2Ygcm93cyBpbiB3b3JkX2RpY3QgYW5kIHRoZSBsZW5ndGggb2YgcGFpcmVkX3dvcmRzIHNob3VsZCBiZSBlcXVhbC4iKQogIH0KICAKICB3b3JkX2RpY3QgPC0gdGliYmxlOjp0aWJibGUod29yZCA9IHdvcmRfZGljdCR2YWx1ZSkKICByZXN1bHQgPC0gdGliYmxlOjp0aWJibGUod29yZCA9IHdvcmRfZGljdCR3b3JkLCBjYXRlZ29yeSA9IHBhaXJlZF93b3JkcykKICByZXR1cm4ocmVzdWx0KQp9CmBgYAoKYGBge3J9CmNsYXNzaWZ5X3RleHQgPC0gZnVuY3Rpb24odGV4dCwgd29yZF9wYWlyKSB7CiAgIyBUb2tlbml6aW5nIHRoZSB0ZXh0IGFuZCBjb252ZXJ0aW5nIHRvIGxvd2VyY2FzZQogIHdvcmRzIDwtIHRvbG93ZXIodW5saXN0KHN0cnNwbGl0KHRleHQsICJcXFcrIikpKQogIAogICMgRmluZGluZyB0aGUgZnJlcXVlbnQgd29yZHMgaW4gdGhlIHRleHQKICBmcmVxX3dvcmRzIDwtIHdvcmRzW3dvcmRzICVpbiUgd29yZF9wYWlyJHdvcmRdCiAgCiAgaWYgKGxlbmd0aChmcmVxX3dvcmRzKSA9PSAwKSB7CiAgICByZXR1cm4oIlVua25vd24iKSAgIyBSZXR1cm5pbmcgVW5rbm93biBpZiBubyBmcmVxdWVudCB3b3JkcyBhcmUgZm91bmQKICB9CiAgCiAgIyBHZXR0aW5nIHRoZSBjb3JyZXNwb25kaW5nIGNhdGVnb3JpZXMKICBjYXRlZ29yaWVzIDwtIHdvcmRfcGFpciRjYXRlZ29yeVt3b3JkX3BhaXIkd29yZCAlaW4lIGZyZXFfd29yZHNdCiAgCiAgIyBTb3J0aW5nIGNhdGVnb3JpZXMgYnkgZnJlcXVlbmN5IGluIGRlc2NlbmRpbmcgb3JkZXIgYW5kIHJldHVybiB0aGUgbW9zdCBmcmVxdWVudCBvbmUKICByZXR1cm4obmFtZXMoc29ydCh0YWJsZShjYXRlZ29yaWVzKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSkKfQpgYGAKCmBgYHtyfQpjb3JwdXNfbWFrZXIgPC0gZnVuY3Rpb24odGV4dCkgewogIHRleHRzIDwtIHRleHQkdGV4dAogIHZjIDwtIFZlY3RvclNvdXJjZSh0ZXh0cykKICBjb3JwdXMgPC0gQ29ycHVzKHZjKQogIAogIHJldHVybihjb3JwdXMpCn0KYGBgCgpgYGB7cn0KZnJlcV90ZXJtcyA8LSBmdW5jdGlvbihjbGVhbl90ZXh0LCBudW1iZXIpIHsKICBkdG0gPC0gRG9jdW1lbnRUZXJtTWF0cml4KGNsZWFuX3RleHQpCiAgZHRtLnRmaWRmIDwtIHdlaWdodFRmSWRmKGR0bSkKICAKICBtZGYgPC0gYXNfdGliYmxlKGFzLm1hdHJpeChkdG0udGZpZGYpKQogIAogIG1kZi5mcmVxIDwtIG1kZiAlPiUKICBzZWxlY3QoZmluZEZyZXFUZXJtcyhkdG0sIG51bWJlcikpICU+JQogIHN1bW1hcmlzZV9hbGwoc3VtKSAlPiUKICBnYXRoZXIoKSAlPiUKICBhcnJhbmdlKGRlc2ModmFsdWUpKQoKICBtZGYuZnJlcSRrZXkgPC0gCiAgICBmYWN0b3IobWRmLmZyZXEka2V5LAogICAgICAgICAgIGxldmVscyA9IG1kZi5mcmVxJGtleVtvcmRlcihtZGYuZnJlcSR2YWx1ZSldKQogIAogIHdvcmRfZGljdGlvbmFyeSA8LSBhc190aWJibGUobWRmLmZyZXEka2V5KQogIAogIGdncGxvdChtZGYuZnJlcSwgYWVzKHg9a2V5LCB5PXZhbHVlKSkgKwogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgICBsYWJzKHg9InRlcm1zIiwgeT0iZnJlcSIpICsgY29vcmRfZmxpcCgpCiAgCiAgcmV0dXJuKHdvcmRfZGljdGlvbmFyeSkKfQpgYGAKCiMgRHVrZSBhbmFseXNpcwoKYGBge3J9CmR1a2VfdGV4dCA8LSBzdWJzZXQoZGF0YV9wb3N0c19jb250ZW50LCBpZCA9PSAiZHVrZS5jc3YiKQpkdWtlX2NvcnB1cyA8LSBjb3JwdXNfbWFrZXIoZHVrZV90ZXh0KQoKZHVrZV9jbGVhbl90ZXh0IDwtY2xlYW51cChkdWtlX2NvcnB1cywgYygibmV3IiwgIndpbGwiLCAiY2hhbmdlIiwgIm5vcnRoIiwgImNhbiIsICJmaXJzdCIsICJ5ZWFyIiwgImNhcm9saW5hIiwgInllYXJzIiwgInN1bW1lciIsICJtZWV0IiwgIm9uZSIpKQoKZHVrZV93b3JkX2RpY3Rpb25hcnkgPC0gZnJlcV90ZXJtcyhkdWtlX2NsZWFuX3RleHQsIDMwKQoKZHVrZV93b3JkX2NhdGVnb3J5IDwtIGMoIkltYWdlIiwgIkVkdWNhdGlvbiIsICJFZHVjYXRpb24iLCAiSW1hZ2UiLCAiSW1hZ2UiLCAiUmVzZWFyY2giLCAiUmVzZWFyY2giLCAiSW1hZ2UiLCAiSW1hZ2UiLCAiRW5nYWdlbWVudCIsICJSZXNlYXJjaCIsICJSZXNlYXJjaCIsICJJbWFnZSIsICJFZHVjYXRpb24iLCAiRWR1Y2F0aW9uIiwgIlNvY2lldHkiLCAiRWR1Y2F0aW9uIiwgIkVuZ2FnZW1lbnQiKQoKZHVrZV93b3JkX3BhaXIgPC0gcGFpcmluZyhkdWtlX3dvcmRfZGljdGlvbmFyeSwgZHVrZV93b3JkX2NhdGVnb3J5KQoKcHJpbnQoZHVrZV93b3JkX3BhaXIpCgojIEFwcGx5aW5nIHRoZSBjbGFzc2lmeV90ZXh0IGZ1bmN0aW9uIHRvIGVhY2ggdGV4dApkdWtlX3RleHQgPC0gZHVrZV90ZXh0ICU+JQogIG11dGF0ZShjYXRlZ29yeSA9IHNhcHBseSh0ZXh0LCBjbGFzc2lmeV90ZXh0LCB3b3JkX3BhaXIgPSBkdWtlX3dvcmRfcGFpcikpCgpwcmludChkdWtlX3RleHQpCmBgYAoKIyBlcGZsCgpgYGB7cn0KZXBmbF90ZXh0IDwtIHN1YnNldChkYXRhX3Bvc3RzX2NvbnRlbnQsIGlkID09ICJlcGZsLmNzdiIpCmVwZmxfY29ycHVzIDwtIGNvcnB1c19tYWtlcihlcGZsX3RleHQpCgplcGZsX2NsZWFuX3RleHQgPC0gY2xlYW51cChlcGZsX2NvcnB1cywgYygibmV3IiwgImFtcCIsICJjYW4iLCAid2lsbCIsICJvbmUiLCAibm93IiwgIuKAkyIsICJ2aWEiLCAicG9ydHJhaXQiKSkKCmVwZmxfd29yZF9kaWN0aW9uYXJ5IDwtIGZyZXFfdGVybXMoZXBmbF9jbGVhbl90ZXh0LCAzMCkKCmVwZmxfd29yZF9jYXRlZ29yeSA8LSBjKCJJbWFnZSIsICJSZXNlYXJjaCIsICJJbWFnZSIsICJSZXNlYXJjaCIsICJSZXNlYXJjaCIsICJSZXNlYXJjaCIsICJFZHVjYXRpb24iLCAiSW1hZ2UiLCAiUmVzZWFyY2giLCAiUmVzZWFyY2giLCAiRWR1Y2F0aW9uIiwgIlJlc2VhcmNoIiwgTkEsICJFZHVjYXRpb24iLCAiRWR1Y2F0aW9uIikKCmVwZmxfd29yZF9wYWlyIDwtIHBhaXJpbmcoZXBmbF93b3JkX2RpY3Rpb25hcnksIGVwZmxfd29yZF9jYXRlZ29yeSkKCnByaW50KGVwZmxfd29yZF9wYWlyKQoKIyBBcHBseWluZyB0aGUgY2xhc3NpZnlfdGV4dCBmdW5jdGlvbiB0byBlYWNoIHRleHQKZXBmbF90ZXh0IDwtIGVwZmxfdGV4dCAlPiUKICBtdXRhdGUoY2F0ZWdvcnkgPSBzYXBwbHkodGV4dCwgY2xhc3NpZnlfdGV4dCwgd29yZF9wYWlyID0gZXBmbF93b3JkX3BhaXIpKQoKcHJpbnQoZXBmbF90ZXh0KQpgYGAKCiMgZ29lCgpgYGB7cn0KZ29lX3RleHQgPC0gc3Vic2V0KGRhdGFfcG9zdHNfY29udGVudCwgaWQgPT0gImdvZS5jc3YiKQpnb2VfY29ycHVzIDwtIGNvcnB1c19tYWtlcihnb2VfdGV4dCkKCmdvZV9jbGVhbl90ZXh0IDwtIGNsZWFudXAoZ29lX2NvcnB1cywgYygiY2FuIiwgIm5ldyIsICJhbXAiLCAid2lsbCIpKQoKZ29lX3dvcmRfZGljdGlvbmFyeSA8LSBmcmVxX3Rlcm1zKGdvZV9jbGVhbl90ZXh0LCAxNSkKCmdvZV93b3JkX2NhdGVnb3J5IDwtIGMoIlJlc2VhcmNoIiwgIlJlc2VhcmNoIiwgIkltYWdlIiwgIkVkdWNhdGlvbiIsICJFbmdhZ2VtZW50IiwgIkltYWdlIiwgIkVuZ2FnZW1lbnQiLCAiRWR1Y2F0aW9uIikKCmdvZV93b3JkX3BhaXIgPC0gcGFpcmluZyhnb2Vfd29yZF9kaWN0aW9uYXJ5LCBnb2Vfd29yZF9jYXRlZ29yeSkKCnByaW50KGdvZV93b3JkX3BhaXIpCgojIEFwcGx5aW5nIHRoZSBjbGFzc2lmeV90ZXh0IGZ1bmN0aW9uIHRvIGVhY2ggdGV4dApnb2VfdGV4dCA8LSBnb2VfdGV4dCAlPiUKICBtdXRhdGUoY2F0ZWdvcnkgPSBzYXBwbHkodGV4dCwgY2xhc3NpZnlfdGV4dCwgd29yZF9wYWlyID0gZ29lX3dvcmRfcGFpcikpCgpwcmludChnb2VfdGV4dCkKYGBgCgojIGhhcnZhcmQKCmBgYHtyfQpoYXJ2YXJkX3RleHQgPC0gc3Vic2V0KGRhdGFfcG9zdHNfY29udGVudCwgaWQgPT0gImhhcnZhcmQuY3N2IikKaGFydmFyZF9jb3JwdXMgPC0gY29ycHVzX21ha2VyKGhhcnZhcmRfdGV4dCkKCmhhcnZhcmRfY2xlYW5fdGV4dCA8LSBjbGVhbnVwKGhhcnZhcmRfY29ycHVzLCBjKCJuZXciLCAiY2FuIiwgIndpbGwiLCAic3VtbWVyIiwgInllYXIiLCAiZmlyc3QiLCAibWF5IiwgIi0iLCAicmVjZW50IiwgInllYXJzIiwgIm9uZSIsICJzYWlkIiwgInRpbWUiLCAibWFueSIsICJ3b3JsZCIsICJjaGFuZ2UiKSkKCmhhcnZhcmRfd29yZF9kaWN0aW9uYXJ5IDwtIGZyZXFfdGVybXMoaGFydmFyZF9jbGVhbl90ZXh0LCA1MCkKCmhhcnZhcmRfd29yZF9jYXRlZ29yeSA8LSBjKCJJbWFnZSIsICJFZHVjYXRpb24iLCAiUmVzZWFyY2giLCAiSW1hZ2UiLCAiUmVzZWFyY2giLCAiUmVzZWFyY2giLCAiRW5nYWdlbWVudCIsICJFZHVjYXRpb24iLCAiU29jaWV0eSIsICJFZHVjYXRpb24iLCAiU29jaWV0eSIsICJSZXNlYXJjaCIsICJFZHVjYXRpb24iLCAiSW1hZ2UiLCAiUmVzZWFyY2giLCBOQSwgIlJlc2VhcmNoIiwgIkVkdWNhdGlvbiIsICJFZHVjYXRpb24iLCBOQSwgIlJlc2VhcmNoIiwgIlNvY2lldHkiLCAiU29jaWV0eSIsICJSZXNlYXJjaCIsICJFZHVjYXRpb24iKQoKaGFydmFyZF93b3JkX3BhaXIgPC0gcGFpcmluZyhoYXJ2YXJkX3dvcmRfZGljdGlvbmFyeSwgaGFydmFyZF93b3JkX2NhdGVnb3J5KQoKcHJpbnQoaGFydmFyZF93b3JkX3BhaXIpCgpoYXJ2YXJkX3RleHQgPC0gaGFydmFyZF90ZXh0ICU+JQogIG11dGF0ZShjYXRlZ29yeSA9IHNhcHBseSh0ZXh0LCBjbGFzc2lmeV90ZXh0LCB3b3JkX3BhaXIgPSBoYXJ2YXJkX3dvcmRfcGFpcikpCgpwcmludChoYXJ2YXJkX3RleHQpCmBgYAoKIyBsZWljZXN0ZXIKCmBgYHtyfQpsZWljZXN0ZXJfdGV4dCA8LSBzdWJzZXQoZGF0YV9wb3N0c19jb250ZW50LCBpZCA9PSAibGVpY2VzdGVyLmNzdiIpCmxlaWNlc3Rlcl9jb3JwdXMgPC0gY29ycHVzX21ha2VyKGxlaWNlc3Rlcl90ZXh0KQoKbGVpY2VzdGVyX2NsZWFuX3RleHQgPC0gY2xlYW51cChsZWljZXN0ZXJfY29ycHVzLCBjKCLwn5GJIiwgImRheSIsICJuZXciLCAiY2xlYXIiLCAieWVhciIsICJjYW4iLCAid2lsbCIsICJ0aW1lIiwgInNwYWNlIiwgIm9uZSIsICJmaXJzdCIpKQoKbGVpY2VzdGVyX3dvcmRfZGljdGlvbmFyeSA8LSBmcmVxX3Rlcm1zKGxlaWNlc3Rlcl9jbGVhbl90ZXh0LCA2MCkKCmxlaWNlc3Rlcl93b3JkX2NhdGVnb3J5IDwtIGMoIkltYWdlIiwgTkEsICJJbWFnZSIsICJFbmdhZ2VtZW50IiwgIlNvY2lldHkiLCAiRWR1Y2F0aW9uIiwgIkVkdWNhdGlvbiIsICJSZXNlYXJjaCIsICJFbmdhZ2VtZW50IiwgIkVuZ2FnZW1lbnQiLCAiSW1hZ2UiLCAiRW5nYWdlbWVudCIsICJFZHVjYXRpb24iLCAiRW5nYW1lbWVudCIsICJFbmdhZ2VtZW50IiwgIkVkdWNhdGlvbiIsICJJbWFnZSIsICJFbmdhZ2VtZW50IiwgIkVkdWNhdGlvbiIsIE5BLCAiSW1hZ2UiKQoKbGVpY2VzdGVyX3dvcmRfcGFpciA8LSBwYWlyaW5nKGxlaWNlc3Rlcl93b3JkX2RpY3Rpb25hcnksIGxlaWNlc3Rlcl93b3JkX2NhdGVnb3J5KQoKcHJpbnQobGVpY2VzdGVyX3dvcmRfcGFpcikKCmxlaWNlc3Rlcl90ZXh0IDwtIGxlaWNlc3Rlcl90ZXh0ICU+JQogIG11dGF0ZShjYXRlZ29yeSA9IHNhcHBseSh0ZXh0LCBjbGFzc2lmeV90ZXh0LCB3b3JkX3BhaXIgPSBsZWljZXN0ZXJfd29yZF9wYWlyKSkKCnByaW50KGxlaWNlc3Rlcl90ZXh0KQpgYGAKCiMgbWFuY2hlc3RlcgoKYGBge3J9Cm1hbmNoZXN0ZXJfdGV4dCA8LSBzdWJzZXQoZGF0YV9wb3N0c19jb250ZW50LCBpZCA9PSAibWFuY2hlc3Rlci5jc3YiKQptYW5jaGVzdGVyX2NvcnB1cyA8LSBjb3JwdXNfbWFrZXIobWFuY2hlc3Rlcl90ZXh0KQoKbWFuY2hlc3Rlcl9jbGVhbl90ZXh0IDwtIGNsZWFudXAobWFuY2hlc3Rlcl9jb3JwdXMsIGMoIvCfkYciLCAiY2FuIiwgImp1c3QiLCAid2lsbCIsICJnZXQiLCAid2VsbCIsICJvbmUiLCAiaGVscCIsICJub3ciLCAibmV3IiwgInJlYWQiLCAiY29uZ3JhdHVsYXRpb25zIikpCgptYW5jaGVzdGVyX3dvcmRfZGljdGlvbmFyeSA8LSBmcmVxX3Rlcm1zKG1hbmNoZXN0ZXJfY2xlYW5fdGV4dCwgNTApCgptYW5jaGVzdGVyX3dvcmRfY2F0ZWdvcnkgPC0gYyhOQSwgIkltYWdlIiwgIkVuZ2FnZW1lbnQiLCAiSW1hZ2UiLCAiRWR1Y2F0aW9uIiwgIkVkdWNhdGlvbiIsICJFbmdhZ2VtZW50IiwgIkltYWdlIiwgIkVkdWNhdGlvbiIsICJTb2NpZXR5IiwgIlJlc2VhcmNoIiwgIlJlc2VhcmNoIiwgIkVkdWNhdGlvbiIsICJFZHVjYXRpb24iLCAiRWR1Y2F0aW9uIiwgIkVuZ2FnZW1lbnQiLCAiUmVzZWFyY2giLCAiRWR1Y2F0aW9uIiwgIlJlc2VhcmNoIikKCm1hbmNoZXN0ZXJfd29yZF9wYWlyIDwtIHBhaXJpbmcobWFuY2hlc3Rlcl93b3JkX2RpY3Rpb25hcnksIG1hbmNoZXN0ZXJfd29yZF9jYXRlZ29yeSkKCnByaW50KG1hbmNoZXN0ZXJfd29yZF9wYWlyKQoKbWFuY2hlc3Rlcl90ZXh0IDwtIG1hbmNoZXN0ZXJfdGV4dCAlPiUKICBtdXRhdGUoY2F0ZWdvcnkgPSBzYXBwbHkodGV4dCwgY2xhc3NpZnlfdGV4dCwgd29yZF9wYWlyID0gbWFuY2hlc3Rlcl93b3JkX3BhaXIpKQoKcHJpbnQobWFuY2hlc3Rlcl90ZXh0KQpgYGAKCiMgbWl0CgpgYGB7cn0KbWl0X3RleHQgPC0gc3Vic2V0KGRhdGFfcG9zdHNfY29udGVudCwgaWQgPT0gIm1pdC5jc3YiKQptaXRfY29ycHVzIDwtIGNvcnB1c19tYWtlcihtaXRfdGV4dCkKCm1pdF9jbGVhbl90ZXh0IDwtIGNsZWFudXAobWl0X2NvcnB1cywgYygibmV3IiwgImNhbiIsICJzYXlzIiwgIuKAnCIsICLigJkiLCAi4oCUIiwgIiIsICJtYXkiLCAiZmlyc3QiLCAid2lsbCIsICJ1c2luZyIsICJ3YXkiLCAib25lIiwgInNjaWVuY2UiKSkKCm1pdF93b3JkX2RpY3Rpb25hcnkgPC0gZnJlcV90ZXJtcyhtaXRfY2xlYW5fdGV4dCwgNDApCgptaXRfd29yZF9jYXRlZ29yeSA8LSBjKCJJbWFnZSIsICJSZXNlYXJjaCIsIE5BLCAiSW1hZ2UiLCBOQSwgIkVkdWNhdGlvbiIsICJSZXNlYXJjaCIsIE5BLCAiRWR1Y2F0aW9uIiwgIlJlc2VhcmNoIiwgIlJlc2VhcmNoIiwgIkVkdWNhdGlvbiIsICJSZXNlYXJjaCIsICJSZXNlYXJjaCIsICJJbWFnZSIsICJFZHVjYXRpb24iLCAiRWR1Y2F0aW9uIiwgIlJlc2VhcmNoIiwgIlJlc2VhcmNoIiwgIlJlc2VhcmNoIiwgIlJlc2VhcmNoIiwgIlJlc2VhcmNoIiwgIlJlc2VhcmNoIiwgIlJlc2VhcmNoIiwgIlJlc2VhcmNoIiwgIlNvY2lldHkiKQoKbWl0X3dvcmRfcGFpciA8LSBwYWlyaW5nKG1pdF93b3JkX2RpY3Rpb25hcnksIG1pdF93b3JkX2NhdGVnb3J5KQoKcHJpbnQobWl0X3dvcmRfcGFpcikKCm1pdF90ZXh0IDwtIG1pdF90ZXh0ICU+JQogIG11dGF0ZShjYXRlZ29yeSA9IHNhcHBseSh0ZXh0LCBjbGFzc2lmeV90ZXh0LCB3b3JkX3BhaXIgPSBtaXRfd29yZF9wYWlyKSkKCnByaW50KG1pdF90ZXh0KQpgYGAKCiMgc2IKCmBgYHtyfQpzYl90ZXh0IDwtIHN1YnNldChkYXRhX3Bvc3RzX2NvbnRlbnQsIGlkID09ICJzYi5jc3YiKQpzYl9jb3JwdXMgPC0gY29ycHVzX21ha2VyKHNiX3RleHQpCgpzYl9jbGVhbl90ZXh0IDwtIGNsZWFudXAoc2JfY29ycHVzLCBjKCJuZXciLCAi4oCUIiwgIndpbGwiLCAid2VlayIsICJjYW4iLCAiYW1wIiwgInZpYSIsICJub3ciLCAiZnV0dXJlIiwgImZpcnN0IikpCgpzYl93b3JkX2RpY3Rpb25hcnkgPC0gZnJlcV90ZXJtcyhzYl9jbGVhbl90ZXh0LCAzMCkKCnNiX3dvcmRfY2F0ZWdvcnkgPC0gYygiSW1hZ2UiLCAiUmVzZWFyY2giLCAiSW1hZ2UiLCAiSW1hZ2UiLCAiSW1hZ2UiLCBOQSwgIlJlc2VhcmNoIiwgIkltYWdlIiwgIkltYWdlIiwgIlJlc2VhcmNoIiwgIkVkdWNhdGlvbiIsICJJbWFnZSIsICJFZHVjYXRpb24iLCAiSW1hZ2UiLCAiRWR1Y2F0aW9uIiwgIkltYWdlIiwgIlNvY2lldHkiKQoKc2Jfd29yZF9wYWlyIDwtIHBhaXJpbmcoc2Jfd29yZF9kaWN0aW9uYXJ5LCBzYl93b3JkX2NhdGVnb3J5KQoKcHJpbnQoc2Jfd29yZF9wYWlyKQoKc2JfdGV4dCA8LSBzYl90ZXh0ICU+JQogIG11dGF0ZShjYXRlZ29yeSA9IHNhcHBseSh0ZXh0LCBjbGFzc2lmeV90ZXh0LCB3b3JkX3BhaXIgPSBzYl93b3JkX3BhaXIpKQoKcHJpbnQoc2JfdGV4dCkKYGBgCgojIHN0YW5mb3JkCgpgYGB7cn0Kc3RhbmZvcmRfdGV4dCA8LSBzdWJzZXQoZGF0YV9wb3N0c19jb250ZW50LCBpZCA9PSAic3RhbmZvcmQuY3N2IikKc3RhbmZvcmRfY29ycHVzIDwtIGNvcnB1c19tYWtlcihzdGFuZm9yZF90ZXh0KQoKc3RhbmZvcmRfY2xlYW5fdGV4dCA8LSBjbGVhbnVwKHN0YW5mb3JkX2NvcnB1cywgYygibmV3IiwgIndpbGwiKSkKCnN0YW5mb3JkX3dvcmRfZGljdGlvbmFyeSA8LSBmcmVxX3Rlcm1zKHN0YW5mb3JkX2NsZWFuX3RleHQsIDI1KQoKc3RhbmZvcmRfd29yZF9jYXRlZ29yeSA8LSBjKCJJbWFnZSIsICJJbWFnZSIsICJSZXNlYXJjaCIsICJFZHVjYXRpb24iLCBOQSkKCnN0YW5mb3JkX3dvcmRfcGFpciA8LSBwYWlyaW5nKHN0YW5mb3JkX3dvcmRfZGljdGlvbmFyeSwgc3RhbmZvcmRfd29yZF9jYXRlZ29yeSkKCnByaW50KHN0YW5mb3JkX3dvcmRfcGFpcikKCnN0YW5mb3JkX3RleHQgPC0gc3RhbmZvcmRfdGV4dCAlPiUKICBtdXRhdGUoY2F0ZWdvcnkgPSBzYXBwbHkodGV4dCwgY2xhc3NpZnlfdGV4dCwgd29yZF9wYWlyID0gc3RhbmZvcmRfd29yZF9wYWlyKSkKCnByaW50KHN0YW5mb3JkX3RleHQpCmBgYAoKIyB0cmluaXR5CgpgYGB7cn0KdHJpbml0eV90ZXh0IDwtIHN1YnNldChkYXRhX3Bvc3RzX2NvbnRlbnQsIGlkID09ICJ0cmluaXR5LmNzdiIpCnRyaW5pdHlfY29ycHVzIDwtIGNvcnB1c19tYWtlcih0cmluaXR5X3RleHQpCgp0cmluaXR5X2NsZWFuX3RleHQgPC0gY2xlYW51cCh0cmluaXR5X2NvcnB1cywgYygiYW1wIiwgInJlYWQiLCAibmV3IiwgImNhbiIsICJ3aWxsIiwgIndlZWsiLCAid29yayIsICJncmVhdCIsICJkYXkiLCAidmlzaXQiLCAiaXJpc2giLCAiZmlyc3QiLCAibGVkIiwgImNvbmdyYXR1bGF0aW9ucyIpKQoKdHJpbml0eV93b3JkX2RpY3Rpb25hcnkgPC0gZnJlcV90ZXJtcyh0cmluaXR5X2NsZWFuX3RleHQsIDQwKQoKdHJpbml0eV93b3JkX2NhdGVnb3J5IDwtIGMoIkltYWdlIiwgIkVkdWNhdGlvbiIsICJSZXNlYXJjaCIsICJJbWFnZSIsICJSZXNlYXJjaCIsICJJbWFnZSIsICJSZXNlYXJjaCIsICJTb2NpZXR5IiwgIlNvY2lldHkiLCAiRWR1Y2F0aW9uIiwgIkVuZ2FnZW1lbnQiLCAiRW5nYWdlbWVudCIsICJFZHVjYXRpb24iLCAiSW1hZ2UiLCAiRWR1Y2F0aW9uIiwgIkltYWdlIiwgIlJlc2VhcmNoIikKCnRyaW5pdHlfd29yZF9wYWlyIDwtIHBhaXJpbmcodHJpbml0eV93b3JkX2RpY3Rpb25hcnksIHRyaW5pdHlfd29yZF9jYXRlZ29yeSkKCnByaW50KHRyaW5pdHlfd29yZF9wYWlyKQoKdHJpbml0eV90ZXh0IDwtIHRyaW5pdHlfdGV4dCAlPiUKICBtdXRhdGUoY2F0ZWdvcnkgPSBzYXBwbHkodGV4dCwgY2xhc3NpZnlfdGV4dCwgd29yZF9wYWlyID0gdHJpbml0eV93b3JkX3BhaXIpKQoKcHJpbnQodHJpbml0eV90ZXh0KQpgYGAKCiMgd3YKCmBgYHtyfQp3dl90ZXh0IDwtIHN1YnNldChkYXRhX3Bvc3RzX2NvbnRlbnQsIGlkID09ICJ3di5jc3YiKQp3dl9jb3JwdXMgPC0gY29ycHVzX21ha2VyKHd2X3RleHQpCgp3dl9jbGVhbl90ZXh0IDwtIGNsZWFudXAod3ZfY29ycHVzLCBjKCLwn5Kb8J+SmSIsICLwn5mMIiwgIvCfkYkiLCAiaGFwcHkiLCAiZGF5IiwgInNlZSIsICJ3ZWVrIiwgImdyZWF0IiwgImNhbiIsICJ3aWxsIiwgIndlbGwiLCAia25vdyIsICJuZXciLCAibm93IiwgImdldCIsICJqdXN0IikpCgp3dl93b3JkX2RpY3Rpb25hcnkgPC0gZnJlcV90ZXJtcyh3dl9jbGVhbl90ZXh0LCA0MCkKCnd2X3dvcmRfY2F0ZWdvcnkgPC0gYyhOQSwgIkltYWdlIiwgIkltYWdlIiwgTkEsICJJbWFnZSIsICJJbWFnZSIsICJFbmdhZ2VtZW50IiwgIkVkdWNhdGlvbiIsICJFZHVjYXRpb24iLCAiRWR1Y2F0aW9uIiwgTkEpCgp3dl93b3JkX3BhaXIgPC0gcGFpcmluZyh3dl93b3JkX2RpY3Rpb25hcnksIHd2X3dvcmRfY2F0ZWdvcnkpCgpwcmludCh3dl93b3JkX3BhaXIpCgp3dl90ZXh0IDwtIHd2X3RleHQgJT4lCiAgbXV0YXRlKGNhdGVnb3J5ID0gc2FwcGx5KHRleHQsIGNsYXNzaWZ5X3RleHQsIHdvcmRfcGFpciA9IHd2X3dvcmRfcGFpcikpCgpwcmludCh3dl90ZXh0KQpgYGAKCiMgeWFsZQoKYGBge3J9CnlhbGVfdGV4dCA8LSBzdWJzZXQoZGF0YV9wb3N0c19jb250ZW50LCBpZCA9PSAieWFsZS5jc3YiKQp5YWxlX2NvcnB1cyA8LSBjb3JwdXNfbWFrZXIoeWFsZV90ZXh0KQoKeWFsZV9jbGVhbl90ZXh0IDwtIGNsZWFudXAoeWFsZV9jb3JwdXMsIGMoIm5ldyIsICLigJQiLCAid2lsbCIsICJjYW4iLCAiJyIsICJmaXJzdCIsICJ3b3JrIiwgInJlYWQiLCAiaGVscCIsICJ5ZWFyIikpCgp5YWxlX3dvcmRfZGljdGlvbmFyeSA8LSBmcmVxX3Rlcm1zKHlhbGVfY2xlYW5fdGV4dCwgNzApCgp5YWxlX3dvcmRfY2F0ZWdvcnkgPC0gYygiUmVzZWFyY2giLCAiRWR1Y2F0aW9uIiwgIkVkdWNhdGlvbiIsIE5BLCAiUmVzZWFyY2giLCAiUmVzZWFyY2giLCAiUmVzZWFyY2giLCAiUmVzZWFyY2giLCAiRWR1Y2F0aW9uIiwgIkltYWdlIiwgIlJlc2VhcmNoIiwgIkVkdWNhdGlvbiIsICJJbWFnZSIsICJSZXNlYXJjaCIsIE5BLCAiUmVzZWFyY2giLCAiSW1hZ2UiLCAiSW1hZ2UiLCAiUmVzZWFyY2giLCAiSW1hZ2UiLCAiUmVzZWFyY2giLCAiU29jaWV0eSIsICJSZXNlYXJjaCIsICJTb2NpZXR5IiwgIlNvY2lldHkiKQoKeWFsZV93b3JkX3BhaXIgPC0gcGFpcmluZyh5YWxlX3dvcmRfZGljdGlvbmFyeSwgeWFsZV93b3JkX2NhdGVnb3J5KQoKcHJpbnQoeWFsZV93b3JkX3BhaXIpCgp5YWxlX3RleHQgPC0geWFsZV90ZXh0ICU+JQogIG11dGF0ZShjYXRlZ29yeSA9IHNhcHBseSh0ZXh0LCBjbGFzc2lmeV90ZXh0LCB3b3JkX3BhaXIgPSB5YWxlX3dvcmRfcGFpcikpCgpwcmludCh5YWxlX3RleHQpCmBgYAoK